From 221f6c5d5eeaea4cc21c5d77fba42e6b2911790d Mon Sep 17 00:00:00 2001 From: Dmitri Karpovich Date: Mon, 11 Sep 2023 16:08:56 +0200 Subject: [PATCH 1/2] [bybit] Upgrade unauthorized endpoint to api v5 --- pom.xml | 7 + xchange-bybit/http-client.env.json | 5 + xchange-bybit/lombok.config | 2 + xchange-bybit/pom.xml | 105 ++++++++++++-- .../java/org/knowm/xchange/bybit/Bybit.java | 45 ++++-- .../knowm/xchange/bybit/BybitAdapters.java | 87 ++++++++++-- .../xchange/bybit/BybitErrorAdapter.java | 32 +++++ .../knowm/xchange/bybit/BybitExchange.java | 46 +++++-- .../xchange/bybit/BybitExchangeMetadata.java | 24 ++++ .../BybitJacksonObjectMapperFactory.java | 22 +++ .../converter/StringToBooleanConverter.java | 14 ++ .../converter/StringToCurrencyConverter.java | 15 ++ .../TimestampNanoToInstantConverter.java | 15 ++ .../TimestampSecondsToInstantConverter.java | 15 ++ .../bybit/dto/BybitCategorizedPayload.java | 33 +++++ .../knowm/xchange/bybit/dto/BybitResult.java | 46 ++++--- .../dto/marketdata/BybitInstrumentInfo.java | 123 +++++++++++++++++ .../bybit/dto/marketdata/BybitOrderBook.java | 45 ++++++ .../bybit/dto/marketdata/BybitServerTime.java | 25 ++++ .../bybit/dto/marketdata/BybitSymbol.java | 103 -------------- .../bybit/dto/marketdata/BybitTicker.java | 81 +++-------- .../bybit/mappers/MarketDataMapper.java | 34 ----- .../bybit/service/BybitBaseService.java | 5 + .../xchange/bybit/service/BybitException.java | 65 ++++----- .../BybitJacksonObjectMapperFactory.java | 16 --- .../bybit/service/BybitMarketDataService.java | 89 ++++++++---- .../service/BybitMarketDataServiceRaw.java | 50 +++++-- xchange-bybit/src/main/resources/bybit.json | 4 +- .../xchange/bybit/BybitExchangeTest.java | 63 +++++---- .../xchange/bybit/BybitExchangeWiremock.java | 62 +++++++++ .../bybit/service/BybitErrorsTest.java | 23 ++++ .../BybitMarketDataServiceRawTest.java | 106 +++++++------- .../service/BybitMarketDataServiceTest.java | 130 ++++++++++++------ .../__files/exceptions/v5_market_tickers.json | 7 + .../__files/v5_market_instruments-info.json | 49 +++++++ .../__files/v5_market_orderbook.json | 31 +++++ .../__files/v5_market_tickers-btcusdt.json | 26 ++++ .../resources/__files/v5_market_tickers.json | 55 ++++++++ .../resources/__files/v5_market_time.json | 10 ++ .../src/test/resources/getSymbols.json5 | 63 --------- .../src/test/resources/getTicker.json5 | 37 ----- .../exceptions/v5_market_tickers.json | 15 ++ .../mappings/v5_market_instruments-info.json | 15 ++ .../mappings/v5_market_orderbook.json | 15 ++ .../mappings/v5_market_tickers-btcusdt.json | 15 ++ .../resources/mappings/v5_market_tickers.json | 15 ++ .../resources/mappings/v5_market_time.json | 15 ++ .../test/resources/rest/announcements.http | 2 + .../src/test/resources/rest/market.http | 16 +++ 49 files changed, 1346 insertions(+), 577 deletions(-) create mode 100644 xchange-bybit/http-client.env.json create mode 100644 xchange-bybit/lombok.config create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitErrorAdapter.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchangeMetadata.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/BybitJacksonObjectMapperFactory.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToBooleanConverter.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToCurrencyConverter.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampNanoToInstantConverter.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampSecondsToInstantConverter.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitCategorizedPayload.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitInstrumentInfo.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitOrderBook.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitServerTime.java delete mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitSymbol.java delete mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/mappers/MarketDataMapper.java delete mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitJacksonObjectMapperFactory.java create mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeWiremock.java create mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitErrorsTest.java create mode 100644 xchange-bybit/src/test/resources/__files/exceptions/v5_market_tickers.json create mode 100644 xchange-bybit/src/test/resources/__files/v5_market_instruments-info.json create mode 100644 xchange-bybit/src/test/resources/__files/v5_market_orderbook.json create mode 100644 xchange-bybit/src/test/resources/__files/v5_market_tickers-btcusdt.json create mode 100644 xchange-bybit/src/test/resources/__files/v5_market_tickers.json create mode 100644 xchange-bybit/src/test/resources/__files/v5_market_time.json delete mode 100644 xchange-bybit/src/test/resources/getSymbols.json5 delete mode 100644 xchange-bybit/src/test/resources/getTicker.json5 create mode 100644 xchange-bybit/src/test/resources/mappings/exceptions/v5_market_tickers.json create mode 100644 xchange-bybit/src/test/resources/mappings/v5_market_instruments-info.json create mode 100644 xchange-bybit/src/test/resources/mappings/v5_market_orderbook.json create mode 100644 xchange-bybit/src/test/resources/mappings/v5_market_tickers-btcusdt.json create mode 100644 xchange-bybit/src/test/resources/mappings/v5_market_tickers.json create mode 100644 xchange-bybit/src/test/resources/mappings/v5_market_time.json create mode 100644 xchange-bybit/src/test/resources/rest/announcements.http create mode 100644 xchange-bybit/src/test/resources/rest/market.http diff --git a/pom.xml b/pom.xml index 9e3af921e02..84826f03ec4 100644 --- a/pom.xml +++ b/pom.xml @@ -326,6 +326,13 @@ test + + org.junit.jupiter + junit-jupiter-engine + ${version.junit} + test + + ch.qos.logback diff --git a/xchange-bybit/http-client.env.json b/xchange-bybit/http-client.env.json new file mode 100644 index 00000000000..1188259b82a --- /dev/null +++ b/xchange-bybit/http-client.env.json @@ -0,0 +1,5 @@ +{ + "default": { + "base_url": "https://api.bybit.com" + } +} \ No newline at end of file diff --git a/xchange-bybit/lombok.config b/xchange-bybit/lombok.config new file mode 100644 index 00000000000..22b090cc4b6 --- /dev/null +++ b/xchange-bybit/lombok.config @@ -0,0 +1,2 @@ +lombok.equalsAndHashCode.callSuper = call +lombok.tostring.callsuper = call \ No newline at end of file diff --git a/xchange-bybit/pom.xml b/xchange-bybit/pom.xml index 3a4c4d301dc..4a2aa414140 100644 --- a/xchange-bybit/pom.xml +++ b/xchange-bybit/pom.xml @@ -14,21 +14,18 @@ XChange Bybit XChange implementation for Bybit - http://knowm.org/open-source/xchange/ - 2022 + + 3.2.1 + 5.3.1 + 3.2.0 + - - Knowm Inc. - http://knowm.org/open-source/xchange/ - - - - org.knowm.xchange - xchange-core - ${project.version} + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${version.fasterxml} @@ -36,6 +33,90 @@ wiremock-jre8 + + io.github.resilience4j + resilience4j-ratelimiter + ${version.resilience4j} + + + org.slf4j + slf4j-api + + + + + + io.github.resilience4j + resilience4j-retry + ${version.resilience4j} + + + org.slf4j + slf4j-api + + + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.knowm.xchange + xchange-core + ${project.version} + + - \ No newline at end of file + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + ${version.sortpom-maven-plugin} + + true + false + groupId,artifactId + true + 4 + groupId,artifactId + false + + + + + sort + + verify + + + + + + maven-enforcer-plugin + ${version.maven-enforcer-plugin} + + + enforce + + enforce + + + + + + + + + + + + + + + + diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/Bybit.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/Bybit.java index cfcb9778c52..bccb4b8e4a1 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/Bybit.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/Bybit.java @@ -1,14 +1,16 @@ package org.knowm.xchange.bybit; -import java.io.IOException; -import java.util.List; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.core.MediaType; +import java.io.IOException; +import org.knowm.xchange.bybit.dto.BybitCategorizedPayload; import org.knowm.xchange.bybit.dto.BybitResult; -import org.knowm.xchange.bybit.dto.marketdata.BybitSymbol; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo; +import org.knowm.xchange.bybit.dto.marketdata.BybitOrderBook; +import org.knowm.xchange.bybit.dto.marketdata.BybitServerTime; import org.knowm.xchange.bybit.dto.marketdata.BybitTicker; import org.knowm.xchange.bybit.service.BybitException; @@ -16,18 +18,35 @@ @Produces(MediaType.APPLICATION_JSON) public interface Bybit { - /** - * @apiSpec API - */ + + @GET + @Path("/v5/market/time") + BybitResult getServerTime() throws IOException, BybitException; + + @GET - @Path("/v2/public/tickers") - BybitResult> getTicker24h(@QueryParam("symbol") String symbol) throws IOException, BybitException; + @Path("/v5/market/instruments-info") + BybitResult> getInstrumentsInfo( + @QueryParam("category") String category + ) throws IOException, BybitException; + + + @GET + @Path("/v5/market/tickers") + BybitResult> getTickers( + @QueryParam("category") String category, + @QueryParam("symbol") String symbol + ) throws IOException, BybitException; + - /** - * @apiSpec API - */ @GET - @Path("/v2/public/symbols") - BybitResult> getSymbols() throws IOException, BybitException; + @Path("/v5/market/orderbook") + BybitResult getOrderBook( + @QueryParam("category") String category, + @QueryParam("symbol") String symbol, + @QueryParam("limit") Integer limit + ) throws IOException, BybitException; + + } \ No newline at end of file diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAdapters.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAdapters.java index 9178a3b8929..d00d3ecfc9f 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAdapters.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAdapters.java @@ -1,22 +1,32 @@ package org.knowm.xchange.bybit; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import lombok.experimental.UtilityClass; import org.knowm.xchange.bybit.dto.BybitResult; import org.knowm.xchange.bybit.dto.account.BybitBalance; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo; +import org.knowm.xchange.bybit.dto.marketdata.BybitOrderBook; +import org.knowm.xchange.bybit.dto.marketdata.BybitTicker; import org.knowm.xchange.bybit.dto.trade.BybitOrderDetails; import org.knowm.xchange.bybit.service.BybitException; import org.knowm.xchange.currency.Currency; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.account.Balance; import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.meta.InstrumentMetaData; import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.instrument.Instrument; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.List; - +@UtilityClass public class BybitAdapters { public static final List QUOTE_CURRENCIES = Arrays.asList("USDT", "USDC", "BTC", "DAI"); @@ -83,11 +93,64 @@ public static LimitOrder adaptBybitOrderDetails(BybitOrderDetails bybitOrderResu } public static BybitException createBybitExceptionFromResult(BybitResult walletBalances) { - return new BybitException( - walletBalances.getRetCode(), - walletBalances.getRetMsg(), - walletBalances.getExtCode(), - walletBalances.getExtCode() - ); + return BybitException.builder() + .retCode(walletBalances.getRetCode()) + .retMsg(walletBalances.getRetMsg()) + .extInfo(walletBalances.getExtInfo()) +// .result(walletBalances.getResult()) + .timestamp(walletBalances.getTimestamp()) + .build(); } + + + public static String toSymbol(Instrument instrument) { + if (instrument == null) { + return null; + } + else { + return instrument.getBase().getCurrencyCode() + instrument.getCounter().getCurrencyCode(); + } + } + + public Ticker toTicker(BybitTicker gateioTicker, Instrument instrument) { + return new Ticker.Builder() + .instrument(instrument) + .last(gateioTicker.getLastPrice()) + .bid(gateioTicker.getBestBidPrice()) + .ask(gateioTicker.getBestAskPrice()) + .high(gateioTicker.getHighPrice()) + .low(gateioTicker.getLowPrice()) + .volume(gateioTicker.getVolume24h()) + .quoteVolume(gateioTicker.getTurnover24h()) + .percentageChange(gateioTicker.getPrice24hPercentageChange()) + .build(); + } + + + public static InstrumentMetaData toInstrumentMetaData(BybitInstrumentInfo bybitInstrumentInfo) { + return new InstrumentMetaData.Builder() + .minimumAmount(bybitInstrumentInfo.getLotSizeFilter().getMinOrderQty()) + .maximumAmount(bybitInstrumentInfo.getLotSizeFilter().getMaxOrderQty()) + .counterMinimumAmount(bybitInstrumentInfo.getLotSizeFilter().getMinOrderAmt()) + .counterMaximumAmount(bybitInstrumentInfo.getLotSizeFilter().getMaxOrderAmt()) + .priceScale(bybitInstrumentInfo.getPriceFilter().getTickSize().scale()) + .volumeScale(bybitInstrumentInfo.getLotSizeFilter().getBasePrecision().scale()) + .amountStepSize(bybitInstrumentInfo.getLotSizeFilter().getBasePrecision()) + .priceStepSize(bybitInstrumentInfo.getPriceFilter().getTickSize()) + .build(); + } + + + public OrderBook toOrderBook(BybitOrderBook bybitOrderBook, Instrument instrument) { + List asks = bybitOrderBook.getAsks().stream() + .map(priceSizeEntry -> new LimitOrder(OrderType.ASK, priceSizeEntry.getSize(), instrument, null, null, priceSizeEntry.getPrice())) + .collect(Collectors.toList()); + + List bids = bybitOrderBook.getBids().stream() + .map(priceSizeEntry -> new LimitOrder(OrderType.BID, priceSizeEntry.getSize(), instrument, null, null, priceSizeEntry.getPrice())) + .collect(Collectors.toList()); + + return new OrderBook(Date.from(bybitOrderBook.getTimestamp()), asks, bids); + } + } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitErrorAdapter.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitErrorAdapter.java new file mode 100644 index 00000000000..2cf057be6d5 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitErrorAdapter.java @@ -0,0 +1,32 @@ +package org.knowm.xchange.bybit; + +import java.util.HashMap; +import java.util.Map; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import org.knowm.xchange.bybit.service.BybitException; +import org.knowm.xchange.exceptions.ExchangeException; +import org.knowm.xchange.exceptions.InstrumentNotValidException; + +@UtilityClass +public class BybitErrorAdapter { + + private final Map> EXCEPTION_BY_MESSAGE = new HashMap<>(); + + static { + EXCEPTION_BY_MESSAGE.put("Not supported symbols", InstrumentNotValidException.class); + } + + @SneakyThrows + public ExchangeException adapt(BybitException e) { + + if (EXCEPTION_BY_MESSAGE.containsKey(e.getRetMsg())) { + Class a = EXCEPTION_BY_MESSAGE.get(e.getRetMsg()); + return a.getConstructor(String.class, Throwable.class).newInstance(e.getMessage(), e); + } + + return new ExchangeException(e.getMessage(), e); + + } + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchange.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchange.java index 6639acabb13..e1274883195 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchange.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchange.java @@ -2,17 +2,22 @@ import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; import org.knowm.xchange.BaseExchange; import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.bybit.dto.marketdata.BybitSymbol; -import org.knowm.xchange.bybit.mappers.MarketDataMapper; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo; import org.knowm.xchange.bybit.service.BybitAccountService; import org.knowm.xchange.bybit.service.BybitMarketDataService; import org.knowm.xchange.bybit.service.BybitMarketDataServiceRaw; import org.knowm.xchange.bybit.service.BybitTradeService; -import org.knowm.xchange.exceptions.ExchangeException; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.instrument.Instrument; +@Slf4j public class BybitExchange extends BaseExchange { @Override @@ -34,12 +39,35 @@ public ExchangeSpecification getDefaultExchangeSpecification() { } @Override - public void remoteInit() throws IOException, ExchangeException { + public void remoteInit() throws IOException { //initialize currency pairs - List symbols = ((BybitMarketDataServiceRaw) marketDataService).getSymbols().getResult(); - symbols.forEach(bybitSymbol -> exchangeMetaData.getInstruments().put( - MarketDataMapper.symbolToCurrencyPair(bybitSymbol), - MarketDataMapper.symbolToCurrencyPairMetaData(bybitSymbol)) - ); + List instrumentsInfo = ((BybitMarketDataServiceRaw) marketDataService).getInstrumentsInfo().getList(); + + Map instruments = new HashMap<>(); + Map currencyPairBySymbol = new HashMap<>(); + instrumentsInfo.forEach(bybitInstrumentInfo -> { + instruments.put( + bybitInstrumentInfo.getCurrencyPair(), + BybitAdapters.toInstrumentMetaData(bybitInstrumentInfo)); + + currencyPairBySymbol.put(bybitInstrumentInfo.getSymbol(), + bybitInstrumentInfo.getCurrencyPair()); + }); + + exchangeMetaData = new BybitExchangeMetadata(instruments, null, null, null, null, currencyPairBySymbol); + } + + + + public CurrencyPair toCurrencyPair(String symbol) { + // do remote init again if infos expired + BybitExchangeMetadata bybitExchangeMetadata = (BybitExchangeMetadata) getExchangeMetaData(); + + if (!bybitExchangeMetadata.getCurrencyPairBySymbol().containsKey(symbol)) { + log.debug("Can't parse symbol {}", symbol); + } + + return bybitExchangeMetadata.getCurrencyPairBySymbol().get(symbol); + } } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchangeMetadata.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchangeMetadata.java new file mode 100644 index 00000000000..fb700817444 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitExchangeMetadata.java @@ -0,0 +1,24 @@ +package org.knowm.xchange.bybit; + +import java.util.Map; +import lombok.Value; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.meta.CurrencyMetaData; +import org.knowm.xchange.dto.meta.ExchangeMetaData; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.dto.meta.RateLimit; +import org.knowm.xchange.instrument.Instrument; + +@Value +public class BybitExchangeMetadata extends ExchangeMetaData { + + Map currencyPairBySymbol; + + public BybitExchangeMetadata(Map instruments, + Map currency, RateLimit[] publicRateLimits, + RateLimit[] privateRateLimits, Boolean shareRateLimits, Map currencyPairBySymbol) { + super(instruments, currency, publicRateLimits, privateRateLimits, shareRateLimits); + this.currencyPairBySymbol = currencyPairBySymbol; + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/BybitJacksonObjectMapperFactory.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/BybitJacksonObjectMapperFactory.java new file mode 100644 index 00000000000..6b715863fb1 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/BybitJacksonObjectMapperFactory.java @@ -0,0 +1,22 @@ +package org.knowm.xchange.bybit.config; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import si.mazi.rescu.serialization.jackson.DefaultJacksonObjectMapperFactory; + +public class BybitJacksonObjectMapperFactory extends DefaultJacksonObjectMapperFactory { + + @Override + public void configureObjectMapper(ObjectMapper objectMapper) { + super.configureObjectMapper(objectMapper); + + // by default read timetamps as milliseconds + objectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); + + // enable parsing to Instant + objectMapper.registerModule(new JavaTimeModule()); + + } + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToBooleanConverter.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToBooleanConverter.java new file mode 100644 index 00000000000..d6c6aa8e9ed --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToBooleanConverter.java @@ -0,0 +1,14 @@ +package org.knowm.xchange.bybit.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; + +/** + * Converts string value "1" to {@code true}, rest to {@code false} + */ +public class StringToBooleanConverter extends StdConverter { + + @Override + public Boolean convert(final String value) { + return "1".equals(value); + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToCurrencyConverter.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToCurrencyConverter.java new file mode 100644 index 00000000000..9b2da9a4afe --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/StringToCurrencyConverter.java @@ -0,0 +1,15 @@ +package org.knowm.xchange.bybit.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import org.knowm.xchange.currency.Currency; + +/** + * Converts string value {@code Currency} + */ +public class StringToCurrencyConverter extends StdConverter { + + @Override + public Currency convert(String value) { + return Currency.getInstance(value); + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampNanoToInstantConverter.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampNanoToInstantConverter.java new file mode 100644 index 00000000000..8dedc6ae699 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampNanoToInstantConverter.java @@ -0,0 +1,15 @@ +package org.knowm.xchange.bybit.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import java.time.Instant; + +/** + * Converts timestamp in seconds to {@code Instant} + */ +public class TimestampNanoToInstantConverter extends StdConverter { + + @Override + public Instant convert(final Long value) { + return Instant.ofEpochSecond(0L, value); + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampSecondsToInstantConverter.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampSecondsToInstantConverter.java new file mode 100644 index 00000000000..8f79de04469 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/config/converter/TimestampSecondsToInstantConverter.java @@ -0,0 +1,15 @@ +package org.knowm.xchange.bybit.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import java.time.Instant; + +/** + * Converts timestamp in seconds to {@code Instant} + */ +public class TimestampSecondsToInstantConverter extends StdConverter { + + @Override + public Instant convert(final Long value) { + return Instant.ofEpochSecond(value); + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitCategorizedPayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitCategorizedPayload.java new file mode 100644 index 00000000000..4734658f6d4 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitCategorizedPayload.java @@ -0,0 +1,33 @@ +package org.knowm.xchange.bybit.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +@Data +public class BybitCategorizedPayload { + + @JsonProperty("category") + Category category; + + @JsonProperty("list") + List list; + + + @Getter + @AllArgsConstructor + public enum Category { + SPOT("spot"), + LINEAR("linear"), + INVERSE("inverse"), + OPTION("option"); + + @JsonValue + private final String value; + + } + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitResult.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitResult.java index 9d9ea219c53..ae0e9db2b6f 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitResult.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/BybitResult.java @@ -1,37 +1,43 @@ package org.knowm.xchange.bybit.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import java.util.Date; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; -import org.knowm.xchange.utils.jackson.UnixTimestampNanoSecondsDeserializer; - -@Builder -@Jacksonized -@Value +import java.time.Instant; +import java.util.Map; +import lombok.Data; +import si.mazi.rescu.ExceptionalReturnContentException; + +@Data public class BybitResult { - @JsonProperty("ret_code") + private static final int SUCCESS_CODE = 0; + + + @JsonProperty("retCode") int retCode; - @JsonProperty("ret_msg") + @JsonProperty("retMsg") String retMsg; - @JsonProperty("ext_code") - String extCode; - - @JsonProperty("ext_info") - String extInfo; + @JsonProperty("retExtInfo") + Map extInfo; @JsonProperty("result") V result; - @JsonProperty("time_now") - @JsonDeserialize(using = UnixTimestampNanoSecondsDeserializer.class) - Date timeNow; + @JsonProperty("time") + Instant timestamp; + + + public void setRetCode(int code) { + if (SUCCESS_CODE != code) { + throw new ExceptionalReturnContentException(String.valueOf(code)); + } + retCode = code; + } + + + //todo: remove public boolean isSuccess() { return retCode == 0; } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitInstrumentInfo.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitInstrumentInfo.java new file mode 100644 index 00000000000..aff9fbab2fe --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitInstrumentInfo.java @@ -0,0 +1,123 @@ +package org.knowm.xchange.bybit.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonValue; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.math.BigDecimal; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bybit.config.converter.StringToBooleanConverter; +import org.knowm.xchange.bybit.config.converter.StringToCurrencyConverter; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; + +@Data +@Builder +@Jacksonized +public class BybitInstrumentInfo { + + @JsonProperty("symbol") + String symbol; + + @JsonProperty("baseCoin") + @JsonDeserialize(converter = StringToCurrencyConverter.class) + Currency base; + + @JsonProperty("quoteCoin") + @JsonDeserialize(converter = StringToCurrencyConverter.class) + Currency counter; + + @JsonProperty("innovation") + @JsonDeserialize(converter = StringToBooleanConverter.class) + Boolean innovation; + + @JsonProperty("status") + Status status; + + @JsonProperty("marginTrading") + MarginTrading marginTrading; + + @JsonProperty("lotSizeFilter") + LotSizeFilter lotSizeFilter; + + @JsonProperty("priceFilter") + PriceFilter priceFilter; + + + public CurrencyPair getCurrencyPair() { + return new CurrencyPair(base, counter); + } + + + @Data + @Builder + @Jacksonized + public static class LotSizeFilter { + + @JsonProperty("basePrecision") + BigDecimal basePrecision; + + @JsonProperty("quotePrecision") + BigDecimal quotePrecision; + + @JsonProperty("minOrderQty") + BigDecimal minOrderQty; + + @JsonProperty("maxOrderQty") + BigDecimal maxOrderQty; + + @JsonProperty("minOrderAmt") + BigDecimal minOrderAmt; + + @JsonProperty("maxOrderAmt") + BigDecimal maxOrderAmt; + + } + + + @Data + @Builder + @Jacksonized + public static class PriceFilter { + + @JsonProperty("tickSize") + BigDecimal tickSize; + + } + + + + @Getter + @AllArgsConstructor + public enum Status { + PRE_LAUNCH("PreLaunch"), + TRADING("Trading"), + SETTLING("Settling"), + DELIVERING("DELIVERING"), + CLOSED("Closed"); + + @JsonValue + private final String value; + + } + + + @Getter + @AllArgsConstructor + public enum MarginTrading { + NONE("none"), + BOTH("both"), + UTA_ONLY("utaOnly"), + NORMAL_SPOT_ONLY("normalSpotOnly"); + + @JsonValue + private final String value; + + + } + + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitOrderBook.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitOrderBook.java new file mode 100644 index 00000000000..fbfca3940b4 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitOrderBook.java @@ -0,0 +1,45 @@ +package org.knowm.xchange.bybit.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.List; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Builder +@Jacksonized +@Data +public class BybitOrderBook { + + @JsonProperty("s") + String symbol; + + @JsonProperty("a") + List asks; + + @JsonProperty("b") + List bids; + + @JsonProperty("ts") + Instant timestamp; + + @JsonProperty("u") + Long updateId; + + + + @Data + @Builder + @Jacksonized + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + public static class PriceSizeEntry { + + BigDecimal price; + + BigDecimal size; + + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitServerTime.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitServerTime.java new file mode 100644 index 00000000000..3cb6595c631 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitServerTime.java @@ -0,0 +1,25 @@ +package org.knowm.xchange.bybit.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.time.Instant; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bybit.config.converter.TimestampNanoToInstantConverter; +import org.knowm.xchange.bybit.config.converter.TimestampSecondsToInstantConverter; + +@Data +@Builder +@Jacksonized +public class BybitServerTime { + + @JsonProperty("timeSecond") + @JsonDeserialize(converter = TimestampSecondsToInstantConverter.class) + Instant timestamp; + + @JsonProperty("timeNano") + @JsonDeserialize(converter = TimestampNanoToInstantConverter.class) + Instant timestampNano; + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitSymbol.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitSymbol.java deleted file mode 100644 index 280cc753600..00000000000 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitSymbol.java +++ /dev/null @@ -1,103 +0,0 @@ -package org.knowm.xchange.bybit.dto.marketdata; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.math.BigDecimal; -import lombok.Builder; -import lombok.Value; -import lombok.extern.jackson.Jacksonized; - -@Builder -@Jacksonized -@Value -public class BybitSymbol { - - @JsonProperty("name") - String name; - - @JsonProperty("alias") - String alias; - - @JsonProperty("status") - String status; - - @JsonProperty("base_currency") - String baseCurrency; - - @JsonProperty("quote_currency") - String quoteCurrency; - - @JsonProperty("price_scale") - Integer priceScale; - - @JsonProperty("taker_fee") - BigDecimal takerFee; - - @JsonProperty("maker_fee") - BigDecimal makerFee; - - @JsonProperty("funding_interval") - Integer fundingInterval; - - @JsonProperty("leverage_filter") - LeverageFilter leverageFilter; - - @JsonProperty("price_filter") - PriceFilter priceFilter; - - @JsonProperty("lot_size_filter") - LotSizeFilter lotSizeFilter; - - - @Builder - @Jacksonized - @Value - public static class LeverageFilter { - - @JsonProperty("min_leverage") - Integer minLeverage; - - @JsonProperty("max_leverage") - Integer maxLeverage; - - @JsonProperty("leverage_step") - BigDecimal leverageStep; - - } - - - @Builder - @Jacksonized - @Value - public static class PriceFilter { - @JsonProperty("min_price") - BigDecimal minPrice; - - @JsonProperty("max_price") - BigDecimal maxPrice; - - @JsonProperty("tick_size") - BigDecimal tickSize; - } - - - @Builder - @Jacksonized - @Value - public static class LotSizeFilter { - - @JsonProperty("max_trading_qty") - BigDecimal maxTradingQty; - - @JsonProperty("min_trading_qty") - BigDecimal minTradingQty; - - @JsonProperty("qty_step") - BigDecimal qtyStep; - - @JsonProperty("post_only_max_trading_qty") - BigDecimal postOnlyMaxTradingQty; - - } - - -} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitTicker.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitTicker.java index 6a0fd544725..17b615cbc30 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitTicker.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/marketdata/BybitTicker.java @@ -2,92 +2,55 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.math.BigDecimal; -import java.util.Date; import lombok.Builder; -import lombok.Value; +import lombok.Data; import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.currency.CurrencyPair; @Builder @Jacksonized -@Value +@Data public class BybitTicker { @JsonProperty("symbol") String symbol; - @JsonProperty("bid_price") + CurrencyPair currencyPair; + + @JsonProperty("bid1Price") BigDecimal bestBidPrice; - @JsonProperty("ask_price") + @JsonProperty("bid1Size") + BigDecimal bestBidSize; + + @JsonProperty("ask1Price") BigDecimal bestAskPrice; - @JsonProperty("last_price") - BigDecimal lastPrice; + @JsonProperty("ask1Size") + BigDecimal bestAskSize; - @JsonProperty("last_tick_direction") - String lastTickDirection; + @JsonProperty("lastPrice") + BigDecimal lastPrice; - @JsonProperty("prev_price_24h") + @JsonProperty("prevPrice24h") BigDecimal prevPrice24h; - @JsonProperty("price_24h_pcnt") + @JsonProperty("price24hPcnt") BigDecimal price24hPercentageChange; - @JsonProperty("high_price_24h") + @JsonProperty("highPrice24h") BigDecimal highPrice; - @JsonProperty("low_price_24h") + @JsonProperty("lowPrice24h") BigDecimal lowPrice; - @JsonProperty("prev_price_1h") - BigDecimal prevPrice1h; - - @JsonProperty("price_1h_pcnt") - BigDecimal price1hPercentageChange; - - @JsonProperty("mark_price") - BigDecimal markPrice; - - @JsonProperty("index_price") - BigDecimal indexPrice; - - @JsonProperty("open_interest") - BigDecimal openInterest; - - @JsonProperty("open_value") - BigDecimal openValue; - - @JsonProperty("total_turnover") - BigDecimal totalTurnover; - - @JsonProperty("turnover_24h") + @JsonProperty("turnover24h") BigDecimal turnover24h; - @JsonProperty("total_volume") - BigDecimal totalVolume; - - @JsonProperty("volume_24h") + @JsonProperty("volume24h") BigDecimal volume24h; - @JsonProperty("funding_rate") - BigDecimal fundingRate; - - @JsonProperty("predicted_funding_rate") - BigDecimal predictedFundingRate; - - @JsonProperty("next_funding_time") - Date nextFundingTime; - - @JsonProperty("countdown_hour") - Integer countdownHour; - - @JsonProperty("delivery_fee_rate") - BigDecimal deliveryFeeRate; - - @JsonProperty("predicted_delivery_price") - BigDecimal predictedDeliveryPrice; - - @JsonProperty("delivery_time") - Date deliveryTime; + @JsonProperty("usdIndexPrice") + BigDecimal usdIndexPrice; } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/mappers/MarketDataMapper.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/mappers/MarketDataMapper.java deleted file mode 100644 index 313d69d8b76..00000000000 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/mappers/MarketDataMapper.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.knowm.xchange.bybit.mappers; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import org.knowm.xchange.bybit.dto.marketdata.BybitSymbol; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.dto.meta.InstrumentMetaData; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public final class MarketDataMapper { - - public static CurrencyPair symbolToCurrencyPair(BybitSymbol symbol) { - return new CurrencyPair(symbol.getBaseCurrency(), symbol.getQuoteCurrency()); - } - - - public static InstrumentMetaData symbolToCurrencyPairMetaData(BybitSymbol bybitSymbol) { - return new InstrumentMetaData.Builder() - //workaround - get maximum of maker and taker fees - .tradingFee(bybitSymbol.getTakerFee().max(bybitSymbol.getMakerFee())) - .minimumAmount(bybitSymbol.getLotSizeFilter().getMinTradingQty()) - .maximumAmount(bybitSymbol.getLotSizeFilter().getMaxTradingQty()) - //e.g. 0.0010 -> 3 - .volumeScale(Math.max(bybitSymbol.getLotSizeFilter().getQtyStep().stripTrailingZeros().scale(), 0)) - .priceScale(bybitSymbol.getPriceScale()) - .counterMinimumAmount(bybitSymbol.getPriceFilter().getMinPrice()) - .counterMaximumAmount(bybitSymbol.getPriceFilter().getMaxPrice()) - .priceScale(bybitSymbol.getPriceScale()) - .amountStepSize(bybitSymbol.getLotSizeFilter().getQtyStep()) - .build(); - - } - -} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitBaseService.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitBaseService.java index d236d910cd4..a8d932f1fc8 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitBaseService.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitBaseService.java @@ -4,6 +4,8 @@ import org.knowm.xchange.Exchange; import org.knowm.xchange.bybit.Bybit; import org.knowm.xchange.bybit.BybitAuthenticated; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.config.BybitJacksonObjectMapperFactory; import org.knowm.xchange.client.ExchangeRestProxyBuilder; import org.knowm.xchange.service.BaseService; import org.knowm.xchange.utils.nonce.CurrentTimeIncrementalNonceFactory; @@ -12,6 +14,8 @@ public class BybitBaseService implements BaseService { + + protected final BybitExchange bybitExchange; protected final BybitAuthenticated bybitAuthenticated; protected final Bybit bybit; protected final ParamsDigest signatureCreator; @@ -19,6 +23,7 @@ public class BybitBaseService implements BaseService { protected final String apiKey; public BybitBaseService(Exchange exchange) { + bybitExchange = (BybitExchange) exchange; bybit = ExchangeRestProxyBuilder .forInterface(Bybit.class, exchange.getExchangeSpecification()) .clientConfigCustomizer(clientConfig -> clientConfig.setJacksonObjectMapperFactory(new BybitJacksonObjectMapperFactory())) diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitException.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitException.java index 3222e15c10c..9f80696dd59 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitException.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitException.java @@ -1,43 +1,32 @@ package org.knowm.xchange.bybit.service; import com.fasterxml.jackson.annotation.JsonProperty; -import si.mazi.rescu.HttpStatusExceptionSupport; - -public class BybitException extends HttpStatusExceptionSupport { - - private final int retCode; - private final String retMsg; - private final String extCode; - private final String extInfo; - - public BybitException(@JsonProperty("ret_code") int retCode, - @JsonProperty("ret_msg") String retMsg, - @JsonProperty("ext_code") String extCode, - @JsonProperty("ext_info") String extInfo) { - this.retCode = retCode; - this.retMsg = retMsg; - this.extCode = extCode; - this.extInfo = extInfo; - } - - @Override - public String getMessage() { - return "{" + - "retCode=" + retCode + - ", retMsg='" + retMsg + '\'' + - ", extCode='" + extCode + '\'' + - ", extInfo='" + extInfo + '\'' + - '}'; - } - - @Override - public String toString() { - return "BybitException{" + - "retCode=" + retCode + - ", retMsg='" + retMsg + '\'' + - ", extCode='" + extCode + '\'' + - ", extInfo='" + extInfo + '\'' + - '}'; - } +import java.time.Instant; +import java.util.Map; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@Builder +@Jacksonized +public class BybitException extends RuntimeException { + + @JsonProperty("retCode") + int retCode; + + @JsonProperty("retMsg") + String retMsg; + + @JsonProperty("retExtInfo") + Map extInfo; + + @JsonProperty("result") + Map result; + + @JsonProperty("time") + Instant timestamp; + + } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitJacksonObjectMapperFactory.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitJacksonObjectMapperFactory.java deleted file mode 100644 index 7c4ca13d982..00000000000 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitJacksonObjectMapperFactory.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.knowm.xchange.bybit.service; - -import com.fasterxml.jackson.core.JsonParser.Feature; -import com.fasterxml.jackson.databind.ObjectMapper; -import si.mazi.rescu.serialization.jackson.DefaultJacksonObjectMapperFactory; - -public class BybitJacksonObjectMapperFactory extends DefaultJacksonObjectMapperFactory { - - @Override - public void configureObjectMapper(ObjectMapper objectMapper) { - super.configureObjectMapper(objectMapper); - //depending on api version bybit sends jsons with double- and single-quotes - objectMapper.configure(Feature.ALLOW_SINGLE_QUOTES, true); - - } -} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataService.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataService.java index b4104924286..6363732ef6b 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataService.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataService.java @@ -1,54 +1,91 @@ package org.knowm.xchange.bybit.service; import java.io.IOException; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.ArrayUtils; import org.knowm.xchange.bybit.BybitAdapters; import org.knowm.xchange.bybit.BybitExchange; -import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.BybitCategorizedPayload; +import org.knowm.xchange.bybit.dto.marketdata.BybitOrderBook; import org.knowm.xchange.bybit.dto.marketdata.BybitTicker; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.marketdata.MarketDataService; -import org.knowm.xchange.utils.Assert; +import org.knowm.xchange.service.marketdata.params.CurrencyPairsParam; +import org.knowm.xchange.service.marketdata.params.InstrumentsParams; +import org.knowm.xchange.service.marketdata.params.Params; public class BybitMarketDataService extends BybitMarketDataServiceRaw implements MarketDataService { + public static final Integer DEFAULT_ORDER_BOOK_SIZE = 50; + public BybitMarketDataService(BybitExchange exchange) { super(exchange); } + @Override + public Ticker getTicker(CurrencyPair currencyPair, Object... args) throws IOException { + return getTicker((Instrument) currencyPair, args); + } + + @Override public Ticker getTicker(Instrument instrument, Object... args) throws IOException { - Assert.notNull(instrument, "Null instrument"); - - BybitResult> response = getTicker24h(BybitAdapters.convertToBybitSymbol(instrument.toString())); - - if (response.getResult().isEmpty()) { - return new Ticker.Builder().build(); - } else { - BybitTicker bybitTicker = response.getResult().get(0); - return new Ticker.Builder() - .timestamp(response.getTimeNow()) - .instrument(instrument) - .bid(bybitTicker.getBestBidPrice()) - .ask(bybitTicker.getBestAskPrice()) - .volume(bybitTicker.getVolume24h()) - .quoteVolume(bybitTicker.getTurnover24h()) - .last(bybitTicker.getLastPrice()) - .high(bybitTicker.getHighPrice()) - .low(bybitTicker.getLowPrice()) - .open(bybitTicker.getPrevPrice24h()) - .percentageChange(bybitTicker.getPrice24hPercentageChange()) - .build(); + return getTickers((InstrumentsParams) () -> instrument == null ? null : Collections.singletonList(instrument)).stream() + .findFirst() + .orElse(null); + } - } + @Override + public List getTickers(Params params) throws IOException { + Instrument instrument = extractInstrument(params); + + BybitCategorizedPayload bybitTickers = getBybitTickers(instrument); + return bybitTickers.getList().stream() + .filter(bybitTicker -> bybitExchange.toCurrencyPair(bybitTicker.getSymbol()) != null) + .map(bybitTicker -> BybitAdapters.toTicker(bybitTicker, bybitExchange.toCurrencyPair(bybitTicker.getSymbol()))) + .collect(Collectors.toList()); } @Override - public Ticker getTicker(CurrencyPair currencyPair, Object... args) throws IOException { - return getTicker((Instrument) currencyPair, args); + public OrderBook getOrderBook(Params params) throws IOException { + Instrument instrument = extractInstrument(params); + return getOrderBook(instrument); } + + + @Override + public OrderBook getOrderBook(Instrument instrument, Object... args) throws IOException { + Integer limit = (Integer) ArrayUtils.get(args, 0, DEFAULT_ORDER_BOOK_SIZE); + + BybitOrderBook bybitOrderBook = getBybitOrderBook(instrument, limit); + return BybitAdapters.toOrderBook(bybitOrderBook, instrument); + } + + + @Override + public OrderBook getOrderBook(CurrencyPair currencyPair, Object... args) throws IOException { + return getOrderBook((Instrument) currencyPair, args); + } + + + private Instrument extractInstrument(Params params) { + Instrument instrument = null; + if (params != null) { + if (params instanceof CurrencyPairsParam) { + instrument = ((CurrencyPairsParam) params).getCurrencyPairs().stream().findFirst().orElse(null); + } + else if (params instanceof InstrumentsParams) { + instrument = ((InstrumentsParams) params).getInstruments().stream().findFirst().orElse(null); + } + } + return instrument; + } + } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRaw.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRaw.java index 3c8aba80f59..6c6f6b027f8 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRaw.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRaw.java @@ -1,12 +1,15 @@ package org.knowm.xchange.bybit.service; import java.io.IOException; -import java.util.List; import org.knowm.xchange.bybit.BybitAdapters; +import org.knowm.xchange.bybit.BybitErrorAdapter; import org.knowm.xchange.bybit.BybitExchange; -import org.knowm.xchange.bybit.dto.BybitResult; -import org.knowm.xchange.bybit.dto.marketdata.BybitSymbol; +import org.knowm.xchange.bybit.dto.BybitCategorizedPayload; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo; +import org.knowm.xchange.bybit.dto.marketdata.BybitOrderBook; +import org.knowm.xchange.bybit.dto.marketdata.BybitServerTime; import org.knowm.xchange.bybit.dto.marketdata.BybitTicker; +import org.knowm.xchange.instrument.Instrument; public class BybitMarketDataServiceRaw extends BybitBaseService { @@ -14,23 +17,44 @@ public BybitMarketDataServiceRaw(BybitExchange exchange) { super(exchange); } - public BybitResult> getTicker24h(String symbol) throws IOException { - BybitResult> result = bybit.getTicker24h(symbol); - if (!result.isSuccess()) { - throw BybitAdapters.createBybitExceptionFromResult(result); + public BybitCategorizedPayload getInstrumentsInfo() throws IOException { + try { + return bybit.getInstrumentsInfo("spot").getResult(); + } + catch (BybitException e) { + throw BybitErrorAdapter.adapt(e); } - return result; } - public BybitResult> getSymbols() throws IOException { - BybitResult> result = bybit.getSymbols(); + public BybitCategorizedPayload getBybitTickers(Instrument instrument) throws IOException { + try { + return bybit.getTickers("spot", BybitAdapters.toSymbol(instrument)).getResult(); + } + catch (BybitException e) { + throw BybitErrorAdapter.adapt(e); + } + + } + - if (!result.isSuccess()) { - throw BybitAdapters.createBybitExceptionFromResult(result); + public BybitOrderBook getBybitOrderBook(Instrument instrument, Integer limit) throws IOException { + try { + return bybit.getOrderBook("spot", BybitAdapters.toSymbol(instrument), limit).getResult(); + } + catch (BybitException e) { + throw BybitErrorAdapter.adapt(e); } - return result; } + + public BybitServerTime getServerTime() throws IOException { + try { + return bybit.getServerTime().getResult(); + } + catch (BybitException e) { + throw BybitErrorAdapter.adapt(e); + } + } } diff --git a/xchange-bybit/src/main/resources/bybit.json b/xchange-bybit/src/main/resources/bybit.json index bdb9267fd8e..9e26dfeeb6e 100644 --- a/xchange-bybit/src/main/resources/bybit.json +++ b/xchange-bybit/src/main/resources/bybit.json @@ -1,3 +1 @@ -{ - "currency_pairs" : {} -} \ No newline at end of file +{} \ No newline at end of file diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeTest.java index 23059b7d133..c27565a0097 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeTest.java @@ -1,42 +1,47 @@ package org.knowm.xchange.bybit; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.assertj.core.api.Assertions.assertThat; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import jakarta.ws.rs.core.Response.Status; -import org.apache.commons.io.IOUtils; -import org.junit.Test; -import org.knowm.xchange.Exchange; -import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.bybit.service.BaseWiremockTest; - -public class BybitExchangeTest extends BaseWiremockTest { +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.instrument.Instrument; +public class BybitExchangeTest extends BybitExchangeWiremock { @Test - public void testSymbolLoading() throws IOException { - Exchange bybitExchange = createExchange(); + void instruments_initialized() { + Map actual = exchange.getExchangeMetaData().getInstruments(); + + InstrumentMetaData expected = new InstrumentMetaData.Builder() + .minimumAmount(new BigDecimal("0.000048")) + .maximumAmount(new BigDecimal("71.73956243")) + .counterMinimumAmount(new BigDecimal("1")) + .counterMaximumAmount(new BigDecimal("2000000")) + .priceScale(2) + .volumeScale(6) + .amountStepSize(new BigDecimal("0.000001")) + .priceStepSize(new BigDecimal("0.01")) + .build(); + + assertThat(actual.keySet()).hasSize(2); + + assertThat(actual.get(CurrencyPair.BTC_USDT)).usingRecursiveComparison().isEqualTo(expected); + } - stubFor( - get(urlPathEqualTo("/v2/public/symbols")) - .willReturn( - aResponse() - .withStatus(Status.OK.getStatusCode()) - .withHeader("Content-Type", "application/json") - .withBody(IOUtils.resourceToString("/getSymbols.json5", StandardCharsets.UTF_8)) - ) - ); - ExchangeSpecification specification = bybitExchange.getExchangeSpecification(); - specification.setShouldLoadRemoteMetaData(true); - bybitExchange.applySpecification(specification); + @Test + void currency_pair_by_symbol_initialized() { + Map actual = ((BybitExchangeMetadata) exchange.getExchangeMetaData()).getCurrencyPairBySymbol(); - assertThat(bybitExchange.getExchangeMetaData().getInstruments()).hasSize(2); + Map expected = new HashMap<>(); + expected.put("BTCUSDT", CurrencyPair.BTC_USDT); + expected.put("ETHUSDT", CurrencyPair.ETH_USDT); + assertThat(actual).isEqualTo(expected); } + } \ No newline at end of file diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeWiremock.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeWiremock.java new file mode 100644 index 00000000000..cc1bfb70271 --- /dev/null +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitExchangeWiremock.java @@ -0,0 +1,62 @@ +package org.knowm.xchange.bybit; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.recording.RecordSpecBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; + +/** + * Sets up the wiremock for exchange + */ +public abstract class BybitExchangeWiremock { + + protected static BybitExchange exchange; + +// private static final boolean IS_RECORDING = true; + private static final boolean IS_RECORDING = false; + + private static WireMockServer wireMockServer; + + + @BeforeAll + public static void initExchange() { + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + + ExchangeSpecification exSpec = new ExchangeSpecification(BybitExchange.class); + exSpec.setSslUri("http://localhost:" + wireMockServer.port()); + exSpec.setApiKey("a"); + exSpec.setSecretKey("b"); + + + if (IS_RECORDING) { + // use default url and record the requests + wireMockServer.startRecording( + new RecordSpecBuilder() + .forTarget("https://api.bybit.com") + .matchRequestBodyWithEqualToJson() + .extractTextBodiesOver(1L) + .chooseBodyMatchTypeAutomatically() + ); + + } + + exchange = (BybitExchange) ExchangeFactory.INSTANCE.createExchange(exSpec); + + } + + + @AfterAll + public static void stop() { + if (IS_RECORDING) { + wireMockServer.stopRecording(); + } + wireMockServer.stop(); + } + + +} \ No newline at end of file diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitErrorsTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitErrorsTest.java new file mode 100644 index 00000000000..d9eb0716455 --- /dev/null +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitErrorsTest.java @@ -0,0 +1,23 @@ +package org.knowm.xchange.bybit.service; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import org.junit.jupiter.api.Test; +import org.knowm.xchange.bybit.BybitExchangeWiremock; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.exceptions.InstrumentNotValidException; +import org.knowm.xchange.service.marketdata.MarketDataService; + +public class BybitErrorsTest extends BybitExchangeWiremock { + + MarketDataService marketDataService = exchange.getMarketDataService(); + + + @Test + void wrong_symbol() { + assertThatExceptionOfType(InstrumentNotValidException.class) + .isThrownBy(() -> marketDataService.getTicker(new CurrencyPair("BLA/BLA"))); + + } + +} diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRawTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRawTest.java index 8f7ff99b95c..a21e59e8152 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRawTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceRawTest.java @@ -1,61 +1,67 @@ package org.knowm.xchange.bybit.service; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.util.List; -import jakarta.ws.rs.core.Response.Status; -import org.apache.commons.io.IOUtils; -import org.junit.Test; -import org.knowm.xchange.Exchange; -import org.knowm.xchange.bybit.dto.marketdata.BybitSymbol; +import java.time.Instant; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.bybit.BybitExchangeWiremock; +import org.knowm.xchange.bybit.dto.BybitCategorizedPayload; +import org.knowm.xchange.bybit.dto.BybitCategorizedPayload.Category; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo.LotSizeFilter; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo.MarginTrading; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo.PriceFilter; +import org.knowm.xchange.bybit.dto.marketdata.BybitInstrumentInfo.Status; +import org.knowm.xchange.bybit.dto.marketdata.BybitServerTime; +import org.knowm.xchange.currency.Currency; + +public class BybitMarketDataServiceRawTest extends BybitExchangeWiremock { + + BybitMarketDataServiceRaw bybitMarketDataServiceRaw = (BybitMarketDataServiceRaw) exchange.getMarketDataService(); + + @Test + void server_time() throws IOException { + BybitServerTime actual = bybitMarketDataServiceRaw.getServerTime(); + + BybitServerTime expected = BybitServerTime.builder() + .timestamp(Instant.ofEpochSecond(1694037473L)) + .timestampNano(Instant.ofEpochSecond(0L, 1694037473517512495L)) + .build(); + + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + } -public class BybitMarketDataServiceRawTest extends BaseWiremockTest { @Test - public void testGetSymbols() throws Exception { - Exchange bybitExchange = createExchange(); - BybitMarketDataServiceRaw marketDataServiceRaw = (BybitMarketDataServiceRaw) bybitExchange.getMarketDataService(); - - stubFor( - get(urlPathEqualTo("/v2/public/symbols")) - .willReturn( - aResponse() - .withStatus(Status.OK.getStatusCode()) - .withHeader("Content-Type", "application/json") - .withBody(IOUtils.resourceToString("/getSymbols.json5", StandardCharsets.UTF_8)) - ) - ); - - List symbols = marketDataServiceRaw.getSymbols().getResult(); - - assertThat(symbols).hasSize(2); - - BybitSymbol btcusdt = symbols.get(0); - assertThat(btcusdt.getName()).isEqualTo("BTCUSDT"); - assertThat(btcusdt.getAlias()).isEqualTo("BTCUSDT"); - assertThat(btcusdt.getStatus()).isEqualTo("Trading"); - assertThat(btcusdt.getBaseCurrency()).isEqualTo("BTC"); - assertThat(btcusdt.getQuoteCurrency()).isEqualTo("USDT"); - assertThat(btcusdt.getPriceScale()).isEqualTo(2); - assertThat(btcusdt.getTakerFee()).isEqualTo(new BigDecimal("0.0006")); - assertThat(btcusdt.getMakerFee()).isEqualTo(new BigDecimal("0.0001")); - assertThat(btcusdt.getFundingInterval()).isEqualTo(480); - assertThat(btcusdt.getLeverageFilter().getMinLeverage()).isEqualTo(1); - assertThat(btcusdt.getLeverageFilter().getMaxLeverage()).isEqualTo(100); - assertThat(btcusdt.getLeverageFilter().getLeverageStep()).isEqualTo(new BigDecimal("0.01")); - assertThat(btcusdt.getPriceFilter().getMinPrice()).isEqualTo(new BigDecimal("0.5")); - assertThat(btcusdt.getPriceFilter().getMaxPrice()).isEqualTo(new BigDecimal("999999")); - assertThat(btcusdt.getPriceFilter().getTickSize()).isEqualTo(new BigDecimal("0.5")); - assertThat(btcusdt.getLotSizeFilter().getMaxTradingQty()).isEqualTo(new BigDecimal("20")); - assertThat(btcusdt.getLotSizeFilter().getMinTradingQty()).isEqualTo(new BigDecimal("0.001")); - assertThat(btcusdt.getLotSizeFilter().getQtyStep()).isEqualTo(new BigDecimal("0.001")); - assertThat(btcusdt.getLotSizeFilter().getPostOnlyMaxTradingQty()).isEqualTo(new BigDecimal("100")); + void instruments_info() throws IOException { + BybitCategorizedPayload actual = bybitMarketDataServiceRaw.getInstrumentsInfo(); + + BybitInstrumentInfo expected = BybitInstrumentInfo.builder() + .symbol("BTCUSDT") + .base(Currency.BTC) + .counter(Currency.USDT) + .innovation(false) + .status(Status.TRADING) + .marginTrading(MarginTrading.BOTH) + .lotSizeFilter(LotSizeFilter.builder() + .basePrecision(new BigDecimal("0.000001")) + .quotePrecision(new BigDecimal("0.00000001")) + .minOrderQty(new BigDecimal("0.000048")) + .maxOrderQty(new BigDecimal("71.73956243")) + .minOrderAmt(new BigDecimal("1")) + .maxOrderAmt(new BigDecimal("2000000")) + .build()) + .priceFilter(PriceFilter.builder() + .tickSize(new BigDecimal("0.01")) + .build()) + .build(); + + assertThat(actual.getCategory()).isEqualTo(Category.SPOT); + assertThat(actual.getList()).hasSize(2); + + assertThat(actual.getList()).first().usingRecursiveComparison().isEqualTo(expected); } diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceTest.java index 9dbe3d1283a..68822edcfdf 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitMarketDataServiceTest.java @@ -1,56 +1,106 @@ package org.knowm.xchange.bybit.service; -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; import java.time.Instant; -import jakarta.ws.rs.core.Response.Status; -import org.apache.commons.io.IOUtils; -import org.junit.Test; -import org.knowm.xchange.Exchange; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.bybit.BybitExchangeWiremock; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.OrderBook; import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.service.marketdata.MarketDataService; -public class BybitMarketDataServiceTest extends BaseWiremockTest { +public class BybitMarketDataServiceTest extends BybitExchangeWiremock { + + MarketDataService marketDataService = exchange.getMarketDataService(); + + + @Test + void tickers() throws IOException { + List actual = marketDataService.getTickers(null); + + Ticker expected = new Ticker.Builder() + .instrument(CurrencyPair.BTC_USDT) + .last(new BigDecimal("25741.97")) + .ask(new BigDecimal("25741.97")) + .bid(new BigDecimal("25741.96")) + .high(new BigDecimal("26049.11")) + .low(new BigDecimal("25372.77")) + .volume(new BigDecimal("5683.211445")) + .quoteVolume(new BigDecimal("146073704.01447217")) + .percentageChange(new BigDecimal("-0.0003")) + .build(); + + assertThat(actual).hasSize(2); + + assertThat(actual).first().usingRecursiveComparison().isEqualTo(expected); + + } + + + @Test + void ticker() throws IOException { + Ticker actual = marketDataService.getTicker(CurrencyPair.BTC_USDT); + + Ticker expected = new Ticker.Builder() + .instrument(CurrencyPair.BTC_USDT) + .last(new BigDecimal("25838")) + .ask(new BigDecimal("25838.01")) + .bid(new BigDecimal("25838")) + .high(new BigDecimal("26049.11")) + .low(new BigDecimal("25560.01")) + .volume(new BigDecimal("5183.468529")) + .quoteVolume(new BigDecimal("133602935.81431006")) + .percentageChange(new BigDecimal("0.0104")) + .build(); + + + assertThat(actual).usingRecursiveComparison().isEqualTo(expected); + + } + @Test - public void testGetTicker() throws Exception { - Exchange bybitExchange = createExchange(); - MarketDataService marketDataService = bybitExchange.getMarketDataService(); - - stubFor( - get(urlPathEqualTo("/v2/public/tickers")) - .willReturn( - aResponse() - .withStatus(Status.OK.getStatusCode()) - .withHeader("Content-Type", "application/json") - .withBody(IOUtils.resourceToString("/getTicker.json5", StandardCharsets.UTF_8)) - ) - ); - - Ticker ticker = marketDataService.getTicker(CurrencyPair.BTC_USDT); - - assertThat(ticker.getInstrument().toString()).isEqualTo("BTC/USDT"); - assertThat(ticker.getOpen()).isEqualTo(new BigDecimal("21670.00")); - assertThat(ticker.getLast()).isEqualTo(new BigDecimal("21333.00")); - assertThat(ticker.getBid()).isEqualTo(new BigDecimal("21323")); - assertThat(ticker.getAsk()).isEqualTo(new BigDecimal("21334")); - assertThat(ticker.getHigh()).isEqualTo(new BigDecimal("22024.50")); - assertThat(ticker.getLow()).isEqualTo(new BigDecimal("21120.00")); - assertThat(ticker.getVwap()).isNull(); - assertThat(ticker.getVolume()).isEqualTo(new BigDecimal("10028.87")); - assertThat(ticker.getQuoteVolume()).isEqualTo(new BigDecimal("216158761.48")); - assertThat(ticker.getTimestamp()).isEqualTo(Instant.parse("2022-07-10T09:09:11.611Z")); - assertThat(ticker.getBidSize()).isNull(); - assertThat(ticker.getAskSize()).isNull(); - assertThat(ticker.getPercentageChange()).isEqualTo(new BigDecimal("-0.015551")); + void order_book() throws IOException { + OrderBook actual = marketDataService.getOrderBook(CurrencyPair.BTC_USDT, 2); + + List expectedAsks = new ArrayList<>(); + expectedAsks.add(new LimitOrder.Builder(OrderType.ASK, CurrencyPair.BTC_USDT) + .limitPrice(new BigDecimal("26198.39")) + .originalAmount(new BigDecimal("0.786996")) + .build()); + expectedAsks.add(new LimitOrder.Builder(OrderType.ASK, CurrencyPair.BTC_USDT) + .limitPrice(new BigDecimal("26198.44")) + .originalAmount(new BigDecimal("0.3")) + .build()); + + List expectedBids = new ArrayList<>(); + expectedBids.add(new LimitOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT) + .limitPrice(new BigDecimal("26198.38")) + .originalAmount(new BigDecimal("0.332024")) + .build()); + expectedBids.add(new LimitOrder.Builder(OrderType.BID, CurrencyPair.BTC_USDT) + .limitPrice(new BigDecimal("26198.29")) + .originalAmount(new BigDecimal("0.015269")) + .build()); + Date expectedTimestamp = Date.from(Instant.ofEpochMilli(1694123074333L)); + + OrderBook expected = new OrderBook(expectedTimestamp, expectedAsks, expectedBids); + + assertThat(actual) + .usingRecursiveComparison() + .ignoringFieldsMatchingRegexes(".*userReference") + .isEqualTo(expected); } + + } \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/__files/exceptions/v5_market_tickers.json b/xchange-bybit/src/test/resources/__files/exceptions/v5_market_tickers.json new file mode 100644 index 00000000000..e2d83825505 --- /dev/null +++ b/xchange-bybit/src/test/resources/__files/exceptions/v5_market_tickers.json @@ -0,0 +1,7 @@ +{ + "retCode": 10001, + "retMsg": "Not supported symbols", + "result": {}, + "retExtInfo": {}, + "time": 1694126903117 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/__files/v5_market_instruments-info.json b/xchange-bybit/src/test/resources/__files/v5_market_instruments-info.json new file mode 100644 index 00000000000..15ab0c4f836 --- /dev/null +++ b/xchange-bybit/src/test/resources/__files/v5_market_instruments-info.json @@ -0,0 +1,49 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "spot", + "list": [ + { + "symbol": "BTCUSDT", + "baseCoin": "BTC", + "quoteCoin": "USDT", + "innovation": "0", + "status": "Trading", + "marginTrading": "both", + "lotSizeFilter": { + "basePrecision": "0.000001", + "quotePrecision": "0.00000001", + "minOrderQty": "0.000048", + "maxOrderQty": "71.73956243", + "minOrderAmt": "1", + "maxOrderAmt": "2000000" + }, + "priceFilter": { + "tickSize": "0.01" + } + }, + { + "symbol": "ETHUSDT", + "baseCoin": "ETH", + "quoteCoin": "USDT", + "innovation": "0", + "status": "Trading", + "marginTrading": "both", + "lotSizeFilter": { + "basePrecision": "0.00001", + "quotePrecision": "0.0000001", + "minOrderQty": "0.00062", + "maxOrderQty": "1229.2336343", + "minOrderAmt": "1", + "maxOrderAmt": "2000000" + }, + "priceFilter": { + "tickSize": "0.01" + } + } + ] + }, + "retExtInfo": {}, + "time": 1694039377373 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/__files/v5_market_orderbook.json b/xchange-bybit/src/test/resources/__files/v5_market_orderbook.json new file mode 100644 index 00000000000..f282ec34e53 --- /dev/null +++ b/xchange-bybit/src/test/resources/__files/v5_market_orderbook.json @@ -0,0 +1,31 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "s": "BTCUSDT", + "a": [ + [ + "26198.39", + "0.786996" + ], + [ + "26198.44", + "0.3" + ] + ], + "b": [ + [ + "26198.38", + "0.332024" + ], + [ + "26198.29", + "0.015269" + ] + ], + "ts": 1694123074333, + "u": 47685744 + }, + "retExtInfo": {}, + "time": 1694123074333 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/__files/v5_market_tickers-btcusdt.json b/xchange-bybit/src/test/resources/__files/v5_market_tickers-btcusdt.json new file mode 100644 index 00000000000..51df2c730cc --- /dev/null +++ b/xchange-bybit/src/test/resources/__files/v5_market_tickers-btcusdt.json @@ -0,0 +1,26 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "spot", + "list": [ + { + "symbol": "BTCUSDT", + "bid1Price": "25838", + "bid1Size": "3.826184", + "ask1Price": "25838.01", + "ask1Size": "1.182306", + "lastPrice": "25838", + "prevPrice24h": "25571.23", + "price24hPcnt": "0.0104", + "highPrice24h": "26049.11", + "lowPrice24h": "25560.01", + "turnover24h": "133602935.81431006", + "volume24h": "5183.468529", + "usdIndexPrice": "25827.38000001" + } + ] + }, + "retExtInfo": {}, + "time": 1694107346132 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/__files/v5_market_tickers.json b/xchange-bybit/src/test/resources/__files/v5_market_tickers.json new file mode 100644 index 00000000000..8d94a8aeecc --- /dev/null +++ b/xchange-bybit/src/test/resources/__files/v5_market_tickers.json @@ -0,0 +1,55 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "spot", + "list": [ + { + "symbol": "BTCUSDT", + "bid1Price": "25741.96", + "bid1Size": "5.888259", + "ask1Price": "25741.97", + "ask1Size": "0.700556", + "lastPrice": "25741.97", + "prevPrice24h": "25750.21", + "price24hPcnt": "-0.0003", + "highPrice24h": "26049.11", + "lowPrice24h": "25372.77", + "turnover24h": "146073704.01447217", + "volume24h": "5683.211445", + "usdIndexPrice": "25734.91000001" + }, + { + "symbol": "ETHUSDT", + "bid1Price": "1632.39", + "bid1Size": "73.03821", + "ask1Price": "1632.4", + "ask1Size": "5.449", + "lastPrice": "1632.39", + "prevPrice24h": "1630.22", + "price24hPcnt": "0.0013", + "highPrice24h": "1670.34", + "lowPrice24h": "1609.29", + "turnover24h": "86080563.3111", + "volume24h": "52716.15586", + "usdIndexPrice": "1631.99433495" + }, + { + "symbol": "ETHBTC", + "bid1Price": "0.063322", + "bid1Size": "0.05", + "ask1Price": "0.063341", + "ask1Size": "0.489", + "lastPrice": "0.063341", + "prevPrice24h": "0.063439", + "price24hPcnt": "-0.0015", + "highPrice24h": "0.064126", + "lowPrice24h": "0.06323", + "turnover24h": "19.311170759", + "volume24h": "302.828" + } + ] + }, + "retExtInfo": {}, + "time": 1694040503342 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/__files/v5_market_time.json b/xchange-bybit/src/test/resources/__files/v5_market_time.json new file mode 100644 index 00000000000..86af3526cfb --- /dev/null +++ b/xchange-bybit/src/test/resources/__files/v5_market_time.json @@ -0,0 +1,10 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "timeSecond": "1694037473", + "timeNano": "1694037473517512495" + }, + "retExtInfo": {}, + "time": 1694037473517 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/getSymbols.json5 b/xchange-bybit/src/test/resources/getSymbols.json5 deleted file mode 100644 index 1a442c1b100..00000000000 --- a/xchange-bybit/src/test/resources/getSymbols.json5 +++ /dev/null @@ -1,63 +0,0 @@ -{ - 'ret_code': 0, - 'ret_msg': 'OK', - 'ext_code': '', - 'ext_info': '', - 'result': [ - { - 'name': 'BTCUSDT', - 'alias': 'BTCUSDT', - 'status': 'Trading', - 'base_currency': 'BTC', - 'quote_currency': 'USDT', - 'price_scale': 2, - 'taker_fee': '0.0006', - 'maker_fee': '0.0001', - 'funding_interval': 480, - 'leverage_filter': { - 'min_leverage': 1, - 'max_leverage': 100, - 'leverage_step': '0.01' - }, - 'price_filter': { - 'min_price': '0.5', - 'max_price': '999999', - 'tick_size': '0.5' - }, - 'lot_size_filter': { - 'max_trading_qty': 20, - 'min_trading_qty': 0.001, - 'qty_step': 0.001, - 'post_only_max_trading_qty': '100' - } - }, - { - 'name': 'ETHUSDT', - 'alias': 'ETHUSDT', - 'status': 'Trading', - 'base_currency': 'ETH', - 'quote_currency': 'USDT', - 'price_scale': 2, - 'taker_fee': '0.0006', - 'maker_fee': '0.0001', - 'funding_interval': 480, - 'leverage_filter': { - 'min_leverage': 1, - 'max_leverage': 100, - 'leverage_step': '0.01' - }, - 'price_filter': { - 'min_price': '0.05', - 'max_price': '99999.9', - 'tick_size': '0.05' - }, - 'lot_size_filter': { - 'max_trading_qty': 1000, - 'min_trading_qty': 0.01, - 'qty_step': 0.01, - 'post_only_max_trading_qty': '5000' - } - } - ], - 'time_now': '1657475395.487439' -} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/getTicker.json5 b/xchange-bybit/src/test/resources/getTicker.json5 deleted file mode 100644 index 57d24add04c..00000000000 --- a/xchange-bybit/src/test/resources/getTicker.json5 +++ /dev/null @@ -1,37 +0,0 @@ -{ - 'ret_code': 0, - 'ret_msg': 'OK', - 'ext_code': '', - 'ext_info': '', - 'result': [ - { - 'symbol': 'BTCUSDT', - 'bid_price': '21323', - 'ask_price': '21334', - 'last_price': '21333.00', - 'last_tick_direction': 'PlusTick', - 'prev_price_24h': '21670.00', - 'price_24h_pcnt': '-0.015551', - 'high_price_24h': '22024.50', - 'low_price_24h': '21120.00', - 'prev_price_1h': '21307.00', - 'price_1h_pcnt': '0.00122', - 'mark_price': '21331.00', - 'index_price': '21334.53', - 'open_interest': 16028.75, - 'open_value': '0.00', - 'total_turnover': '38884574628.30', - 'turnover_24h': '216158761.48', - 'total_volume': 9588193.5, - 'volume_24h': 10028.87, - 'funding_rate': '0.0001', - 'predicted_funding_rate': '0.0001', - 'next_funding_time': '2022-07-10T16:00:00Z', - 'countdown_hour': 7, - 'delivery_fee_rate': '', - 'predicted_delivery_price': '', - 'delivery_time': '' - } - ], - 'time_now': '1657444151.611671' -} diff --git a/xchange-bybit/src/test/resources/mappings/exceptions/v5_market_tickers.json b/xchange-bybit/src/test/resources/mappings/exceptions/v5_market_tickers.json new file mode 100644 index 00000000000..c3763341bd6 --- /dev/null +++ b/xchange-bybit/src/test/resources/mappings/exceptions/v5_market_tickers.json @@ -0,0 +1,15 @@ +{ + "id" : "bf56bd3b-cc24-4498-8b4a-a403c934f088", + "name" : "v5_market_tickers", + "request" : { + "url" : "/v5/market/tickers?category=spot&symbol=BLABLA", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "exceptions/v5_market_tickers.json" + }, + "uuid" : "bf56bd3b-cc24-4498-8b4a-a403c934f088", + "persistent" : true, + "insertionIndex" : 14 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/mappings/v5_market_instruments-info.json b/xchange-bybit/src/test/resources/mappings/v5_market_instruments-info.json new file mode 100644 index 00000000000..0a62d45d0d6 --- /dev/null +++ b/xchange-bybit/src/test/resources/mappings/v5_market_instruments-info.json @@ -0,0 +1,15 @@ +{ + "id" : "e14f007d-f507-4d7f-a522-d92a71e1fc48", + "name" : "v5_market_instruments-info", + "request" : { + "url" : "/v5/market/instruments-info?category=spot", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "v5_market_instruments-info.json" + }, + "uuid" : "e14f007d-f507-4d7f-a522-d92a71e1fc48", + "persistent" : true, + "insertionIndex" : 4 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/mappings/v5_market_orderbook.json b/xchange-bybit/src/test/resources/mappings/v5_market_orderbook.json new file mode 100644 index 00000000000..b6ee936eb42 --- /dev/null +++ b/xchange-bybit/src/test/resources/mappings/v5_market_orderbook.json @@ -0,0 +1,15 @@ +{ + "id" : "b4aa7050-744d-4f73-8f65-cfde4ee8e54d", + "name" : "v5_market_orderbook", + "request" : { + "url" : "/v5/market/orderbook?category=spot&symbol=BTCUSDT&limit=2", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "v5_market_orderbook.json" + }, + "uuid" : "b4aa7050-744d-4f73-8f65-cfde4ee8e54d", + "persistent" : true, + "insertionIndex" : 6 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/mappings/v5_market_tickers-btcusdt.json b/xchange-bybit/src/test/resources/mappings/v5_market_tickers-btcusdt.json new file mode 100644 index 00000000000..ad70de5ec0f --- /dev/null +++ b/xchange-bybit/src/test/resources/mappings/v5_market_tickers-btcusdt.json @@ -0,0 +1,15 @@ +{ + "id" : "2d657f6e-e523-4104-8fcc-e625c50619a1", + "name" : "v5_market_tickers", + "request" : { + "url" : "/v5/market/tickers?category=spot&symbol=BTCUSDT", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "v5_market_tickers-btcusdt.json" + }, + "uuid" : "2d657f6e-e523-4104-8fcc-e625c50619a1", + "persistent" : true, + "insertionIndex" : 6 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/mappings/v5_market_tickers.json b/xchange-bybit/src/test/resources/mappings/v5_market_tickers.json new file mode 100644 index 00000000000..d01ada55d6d --- /dev/null +++ b/xchange-bybit/src/test/resources/mappings/v5_market_tickers.json @@ -0,0 +1,15 @@ +{ + "id" : "d53e9d2a-c765-4981-b267-0f518e7f6c5a", + "name" : "v5_market_tickers", + "request" : { + "url" : "/v5/market/tickers?category=spot", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "v5_market_tickers.json" + }, + "uuid" : "d53e9d2a-c765-4981-b267-0f518e7f6c5a", + "persistent" : true, + "insertionIndex" : 6 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/mappings/v5_market_time.json b/xchange-bybit/src/test/resources/mappings/v5_market_time.json new file mode 100644 index 00000000000..1cc1394fe23 --- /dev/null +++ b/xchange-bybit/src/test/resources/mappings/v5_market_time.json @@ -0,0 +1,15 @@ +{ + "id" : "e2050f9d-aeff-4653-964e-d0de5b892b73", + "name" : "v5_market_time", + "request" : { + "url" : "/v5/market/time", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "v5_market_time.json" + }, + "uuid" : "e2050f9d-aeff-4653-964e-d0de5b892b73", + "persistent" : true, + "insertionIndex" : 6 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/rest/announcements.http b/xchange-bybit/src/test/resources/rest/announcements.http new file mode 100644 index 00000000000..219bccb8ed9 --- /dev/null +++ b/xchange-bybit/src/test/resources/rest/announcements.http @@ -0,0 +1,2 @@ +### Get Announcement +GET {{base_url}}/v5/announcements/index?locale=en-US \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/rest/market.http b/xchange-bybit/src/test/resources/rest/market.http new file mode 100644 index 00000000000..fa56f09cd51 --- /dev/null +++ b/xchange-bybit/src/test/resources/rest/market.http @@ -0,0 +1,16 @@ +### Get Bybit Server Time +GET {{base_url}}/v5/market/time + + +### Get Instruments Info +GET {{base_url}}/v5/market/instruments-info?category=spot&symbol=BTCUSDT + + +### Get Tickers +GET {{base_url}}/v5/market/tickers?category=spot&symbol=BLABLA + + +### Get Orderbook +GET {{base_url}}/v5/market/orderbook?category=spot&symbol=BTCUSDT + + From 52a6578abde4cd6308a4467d423136e5836a74f6 Mon Sep 17 00:00:00 2001 From: Dmitri Karpovich Date: Fri, 1 Dec 2023 14:39:58 +0100 Subject: [PATCH 2/2] [bybit] Sort pom --- xchange-bybit/pom.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xchange-bybit/pom.xml b/xchange-bybit/pom.xml index 0ad8355cdb2..ce7b404df9b 100644 --- a/xchange-bybit/pom.xml +++ b/xchange-bybit/pom.xml @@ -28,12 +28,6 @@ ${version.fasterxml} - - org.wiremock - wiremock - test - - io.github.resilience4j resilience4j-ratelimiter @@ -70,6 +64,12 @@ ${project.version} + + org.wiremock + wiremock + test + +