From 12c6975608099ae54ee50d4bc5aeb1a9b810cd52 Mon Sep 17 00:00:00 2001 From: rizer1980 <4340180@gmail.com> Date: Sun, 19 May 2024 23:12:07 +0300 Subject: [PATCH 1/3] [bybit-stream] implementation --- .../knowm/xchange/bybit/BybitAdapters.java | 105 +++---- .../xchange/bybit/BybitAuthenticated.java | 74 ++++- .../knowm/xchange/bybit/BybitExchange.java | 33 +- .../knowm/xchange/bybit/BybitResilience.java | 161 ++++++++++ .../dto/account/BybitAccountInfoResponse.java | 17 + .../account/BybitCancelAllOrdersPayload.java | 26 ++ .../account/BybitCancelAllOrdersResponse.java | 18 ++ .../position/BybitSetLeveragePayload.java | 13 + .../position/BybitSwitchModePayload.java | 13 + .../dto/trade/BybitCancelOrderPayload.java | 8 +- .../dto/trade/BybitOpenOrdersPayload.java | 11 + .../xchange/bybit/dto/trade/BybitOrder.java | 109 +++++++ .../dto/trade/BybitPlaceOrderPayload.java | 91 +++++- .../dto/trade/details/BybitHedgeMode.java | 14 + .../dto/trade/details/BybitTimeInForce.java | 19 ++ .../bybit/service/BybitAccountService.java | 60 +++- .../bybit/service/BybitAccountServiceRaw.java | 73 ++++- .../bybit/service/BybitBaseService.java | 9 +- .../bybit/service/BybitMarketDataService.java | 5 +- .../service/BybitMarketDataServiceRaw.java | 5 +- .../bybit/service/BybitTradeService.java | 293 +++++++++++++++--- .../bybit/service/BybitTradeServiceRaw.java | 237 ++++++++++---- .../xchange/bybit/BybitAdaptersTest.java | 4 - .../xchange/bybit/BybitExchangeTest.java | 44 +++ .../org/knowm/xchange/bybit/TradeExample.java | 131 -------- .../examples/BybitCancelAllOrdersExample.java | 78 +++++ .../examples/BybitCancelOrderExample.java | 90 ++++++ .../examples/BybitPlaceOrderExample.java | 85 +++++ .../examples/BybitRateLimiterTestExample.java | 100 ++++++ .../bybit/service/BaseWiremockTest.java | 24 +- .../service/BybitAccountServiceRawTest.java | 27 +- .../service/BybitAccountServiceTest.java | 82 ++++- .../service/BybitTradeServiceRawTest.java | 58 +++- .../bybit/service/BybitTradeServiceTest.java | 200 ++++++------ .../src/test/resources/cancelAllOrders.json5 | 19 ++ .../src/test/resources/cancelOrder.json5 | 10 + .../src/test/resources/changeOrder.json5 | 10 + .../src/test/resources/getOrder.json5 | 55 ++++ xchange-bybit/src/test/resources/logback.xml | 4 +- .../src/test/resources/placeMarketOrder.json5 | 10 + .../src/test/resources/setLeverage.json5 | 7 + .../test/resources/switchPositionMode.json5 | 7 + xchange-stream-bybit/pom.xml | 6 + .../dto/trade/BybitComplexOrderChanges.java | 168 ++++++---- .../trade/BybitComplexPositionChanges.java | 117 ++++++- .../dto/trade/BybitOrderChangesResponse.java | 10 +- .../trade/BybitPositionChangesResponse.java | 87 +++--- .../bybit/BybitStreamAdapters.java | 134 ++++++-- .../bybit/BybitStreamingExchange.java | 22 +- .../BybitStreamingMarketDataService.java | 10 +- .../bybit/BybitStreamingTradeService.java | 12 +- .../bybit/BybitStreamAdaptersTest.java | 240 ++++++++++++++ .../bybit/BybitStreamExample.java | 235 -------------- .../bybit/example/BaseBybitExchange.java | 31 ++ .../example/BybitStreamOrderBookExample.java | 58 ++++ .../BybitStreamPositionChangeExample.java | 90 ++++++ .../example/BybitStreamTestNetExample.java | 106 +++++++ .../src/test/resources/logback.xml | 10 +- .../resources/orderBookDeltaResponse.json5 | 63 ++++ .../resources/orderBookSnapshotResponse.json5 | 31 ++ .../test/resources/orderChangeResponse.json5 | 52 ++++ .../resources/positionChangeResponse.json5 | 43 +++ .../src/test/resources/tradeResponse.json5 | 17 + 63 files changed, 3076 insertions(+), 905 deletions(-) create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitResilience.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitAccountInfoResponse.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersPayload.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersResponse.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSetLeveragePayload.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSwitchModePayload.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOpenOrdersPayload.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOrder.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitHedgeMode.java create mode 100644 xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitTimeInForce.java delete mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/TradeExample.java create mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelAllOrdersExample.java create mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelOrderExample.java create mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitPlaceOrderExample.java create mode 100644 xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitRateLimiterTestExample.java create mode 100644 xchange-bybit/src/test/resources/cancelAllOrders.json5 create mode 100644 xchange-bybit/src/test/resources/cancelOrder.json5 create mode 100644 xchange-bybit/src/test/resources/changeOrder.json5 create mode 100644 xchange-bybit/src/test/resources/getOrder.json5 create mode 100644 xchange-bybit/src/test/resources/placeMarketOrder.json5 create mode 100644 xchange-bybit/src/test/resources/setLeverage.json5 create mode 100644 xchange-bybit/src/test/resources/switchPositionMode.json5 create mode 100644 xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamAdaptersTest.java delete mode 100644 xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamExample.java create mode 100644 xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BaseBybitExchange.java create mode 100644 xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamOrderBookExample.java create mode 100644 xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamPositionChangeExample.java create mode 100644 xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamTestNetExample.java create mode 100644 xchange-stream-bybit/src/test/resources/orderBookDeltaResponse.json5 create mode 100644 xchange-stream-bybit/src/test/resources/orderBookSnapshotResponse.json5 create mode 100644 xchange-stream-bybit/src/test/resources/orderChangeResponse.json5 create mode 100644 xchange-stream-bybit/src/test/resources/positionChangeResponse.json5 create mode 100644 xchange-stream-bybit/src/test/resources/tradeResponse.json5 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 158861aa0b4..fa83025d2d0 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 @@ -2,14 +2,22 @@ import static org.knowm.xchange.bybit.dto.BybitCategory.INVERSE; import static org.knowm.xchange.bybit.dto.BybitCategory.OPTION; +import static org.knowm.xchange.bybit.dto.marketdata.instruments.option.BybitOptionInstrumentInfo.OptionType.CALL; +import static org.knowm.xchange.bybit.dto.marketdata.instruments.option.BybitOptionInstrumentInfo.OptionType.PUT; import java.math.BigDecimal; +import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.TemporalAccessor; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.Locale; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.BybitResult; import org.knowm.xchange.bybit.dto.account.allcoins.BybitAllCoinBalance; @@ -50,7 +58,9 @@ public class BybitAdapters { private static final ThreadLocal OPTION_DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("ddMMMyy")); public static final List QUOTE_CURRENCIES = - Arrays.asList("USDT", "USDC", "EUR", "BTC", "ETH", "DAI", "BRZ"); + Arrays.asList("USDT", "USDC", "USDE", "EUR", "BRL", "PLN", "TRY", "SOL", "BTC", "ETH", "DAI", + "BRZ"); + public static Wallet adaptBybitBalances(List coinWalletBalances) { List balances = new ArrayList<>(coinWalletBalances.size()); @@ -77,10 +87,10 @@ public static Wallet adaptBybitBalances(BybitAllCoinsBalance allCoinsBalance) { } public static BybitSide getSideString(Order.OrderType type) { - if (type == Order.OrderType.ASK) { + if (type == Order.OrderType.ASK || type == OrderType.EXIT_BID) { return BybitSide.SELL; } - if (type == Order.OrderType.BID) { + if (type == Order.OrderType.BID || type == OrderType.EXIT_ASK) { return BybitSide.BUY; } throw new IllegalArgumentException("invalid order type"); @@ -144,6 +154,7 @@ public static String convertToBybitSymbol(Instrument instrument) { } public static CurrencyPair guessSymbol(String symbol) { +//SPOT Only for (String quoteCurrency : QUOTE_CURRENCIES) { if (symbol.endsWith(quoteCurrency)) { int splitIndex = symbol.lastIndexOf(quoteCurrency); @@ -154,27 +165,6 @@ public static CurrencyPair guessSymbol(String symbol) { return new CurrencyPair(symbol.substring(0, splitIndex), symbol.substring(splitIndex)); } - public static Instrument guessSymbol(String symbol, BybitCategory category) { - switch (category) { - case SPOT: - { - return guessSymbol(symbol); - } - case LINEAR: - { - if (symbol.endsWith("USDT")) { - int splitIndex = symbol.lastIndexOf("USDT"); - return new FuturesContract( - (symbol.substring(0, splitIndex) + "/" + symbol.substring(splitIndex) + "/PERP")); - } else if (symbol.endsWith("PERP")) { - int splitIndex = symbol.lastIndexOf("PERP"); - return new FuturesContract((symbol.substring(0, splitIndex) + "/" + "USDC/PERP")); - } - } - } - return null; - } - public static Instrument adaptInstrumentInfo(BybitInstrumentInfo instrumentInfo) { if (instrumentInfo instanceof BybitSpotInstrumentInfo) { return new CurrencyPair(instrumentInfo.getBaseCoin(), instrumentInfo.getQuoteCoin()); @@ -198,7 +188,7 @@ public static Instrument adaptInstrumentInfo(BybitInstrumentInfo instrumentInfo) .expireDate(OPTION_DATE_FORMAT.get().parse(expireDateString)) .strike(strike) .type( - optionInstrumentInfo.getOptionsType().equals(OptionType.CALL) + optionInstrumentInfo.getOptionsType().equals(CALL) ? OptionsContract.OptionType.CALL : OptionsContract.OptionType.PUT) .build(); @@ -264,7 +254,7 @@ public static Order adaptBybitOrderDetails(BybitOrderDetail bybitOrderResult) { case LIMIT: builder = new LimitOrder.Builder( - adaptOrderType(bybitOrderResult), guessSymbol(bybitOrderResult.getSymbol())) + adaptOrderType(bybitOrderResult), guessSymbol(bybitOrderResult.getSymbol())) .limitPrice(bybitOrderResult.getPrice()); break; default: @@ -396,38 +386,45 @@ private static Builder adaptBybitTickerBuilder( public static Instrument convertBybitSymbolToInstrument(String symbol, BybitCategory category) { switch (category) { - case SPOT: - { - return guessSymbol(symbol); - } - case LINEAR: - { - if (symbol.endsWith("USDT")) { - int splitIndex = symbol.lastIndexOf("USDT"); - return new FuturesContract( - new CurrencyPair(symbol.substring(0, splitIndex), "USDT"), "PERP"); - } - if (symbol.endsWith("PERP")) { - int splitIndex = symbol.lastIndexOf("PERP"); - return new FuturesContract( - new CurrencyPair(symbol.substring(0, splitIndex), "USDC"), "PERP"); - } - // USDC Futures - int splitIndex = symbol.lastIndexOf("-"); + case SPOT: { + return guessSymbol(symbol); + } + case LINEAR: { + if (symbol.endsWith("USDT")) { + int splitIndex = symbol.lastIndexOf("USDT"); return new FuturesContract( - new CurrencyPair(symbol.substring(0, splitIndex), "USDC"), - symbol.substring(splitIndex + 1)); + new CurrencyPair(symbol.substring(0, splitIndex), "USDT"), "PERP"); } - case INVERSE: - { - int splitIndex = symbol.lastIndexOf("USD"); - String perp = symbol.length() > splitIndex + 3 ? symbol.substring(splitIndex + 3) : ""; + if (symbol.endsWith("PERP")) { + int splitIndex = symbol.lastIndexOf("PERP"); return new FuturesContract( - new CurrencyPair(symbol.substring(0, splitIndex), "USD"), perp); - } - case OPTION: - { + new CurrencyPair(symbol.substring(0, splitIndex), "USDC"), "PERP"); } + // USDC Futures + int splitIndex = symbol.lastIndexOf("-"); + return new FuturesContract( + new CurrencyPair(symbol.substring(0, splitIndex), "USDC"), + symbol.substring(splitIndex + 1)); + } + case INVERSE: { + int splitIndex = symbol.lastIndexOf("USD"); + String perp = symbol.length() > splitIndex + 3 ? symbol.substring(splitIndex + 3) : ""; + return new FuturesContract( + new CurrencyPair(symbol.substring(0, splitIndex), "USD"), perp); + } + case OPTION: { + DateTimeFormatter dateParser = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("ddLLLyy") + .toFormatter(Locale.US); + DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("yyMMdd"); + String[] tokens = symbol.split("-"); + String base = tokens[0]; + String quote = "USDC"; + String date = dateFormat.format(LocalDate.parse(tokens[1], dateParser)); + BigDecimal strike = new BigDecimal(tokens[2]); + return new OptionsContract(base + "/" + quote + "/" + date + "/" + strike + "/" + tokens[3]); + } } return null; } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAuthenticated.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAuthenticated.java index 4677c430e97..babcba4bfd9 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAuthenticated.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitAuthenticated.java @@ -14,8 +14,13 @@ import jakarta.ws.rs.core.MediaType; import java.io.IOException; import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.account.BybitAccountInfoResponse; +import org.knowm.xchange.bybit.dto.account.BybitCancelAllOrdersPayload; +import org.knowm.xchange.bybit.dto.account.BybitCancelAllOrdersResponse; import org.knowm.xchange.bybit.dto.account.allcoins.BybitAllCoinsBalance; import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRates; +import org.knowm.xchange.bybit.dto.account.position.BybitSetLeveragePayload; +import org.knowm.xchange.bybit.dto.account.position.BybitSwitchModePayload; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitWalletBalance; import org.knowm.xchange.bybit.dto.trade.BybitAmendOrderPayload; import org.knowm.xchange.bybit.dto.trade.BybitCancelOrderPayload; @@ -82,54 +87,91 @@ BybitResult> getOpenOrders( throws IOException, BybitException; /** - * @apiSpec API + * @apiSpec API */ @POST - @Path("/order/create") + @Path("/order/cancel") @Consumes(MediaType.APPLICATION_JSON) - BybitResult placeMarketOrder( + BybitResult cancelOrder( @HeaderParam(X_BAPI_API_KEY) String apiKey, @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp, - BybitPlaceOrderPayload payload) + BybitCancelOrderPayload payload) throws IOException, BybitException; /** - * @apiSpec API + * @apiSpec API */ @POST - @Path("/order/create") + @Path("/order/amend") @Consumes(MediaType.APPLICATION_JSON) - BybitResult placeLimitOrder( + BybitResult amendOrder( @HeaderParam(X_BAPI_API_KEY) String apiKey, @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp, - BybitPlaceOrderPayload payload) + BybitAmendOrderPayload payload) throws IOException, BybitException; /** - * @apiSpec API + * @apiSpec API */ @POST - @Path("/order/cancel") + @Path("/position/set-leverage") @Consumes(MediaType.APPLICATION_JSON) - BybitResult cancelOrder( + BybitResult setLeverage( @HeaderParam(X_BAPI_API_KEY) String apiKey, @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp, - BybitCancelOrderPayload payload) + BybitSetLeveragePayload payload) throws IOException, BybitException; /** - * @apiSpec API + * @apiSpec API */ @POST - @Path("/order/amend") + @Path("/position/switch-mode") @Consumes(MediaType.APPLICATION_JSON) - BybitResult amendOrder( + BybitResult switchMode( @HeaderParam(X_BAPI_API_KEY) String apiKey, @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp, - BybitAmendOrderPayload payload) + BybitSwitchModePayload payload) + throws IOException, BybitException; + + /** + * @apiSpec API + */ + @POST + @Path("/order/create") + @Consumes(MediaType.APPLICATION_JSON) + BybitResult placeOrder( + @HeaderParam(X_BAPI_API_KEY) String apiKey, + @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, + @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp, + BybitPlaceOrderPayload payload) + throws IOException, BybitException; + + /** + * @apiSpec API + */ + @GET + @Path("/account/info") + BybitResult getAccountInfo( + @HeaderParam(X_BAPI_API_KEY) String apiKey, + @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, + @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp) + throws IOException, BybitException; + + /** + * @apiSpec API + */ + @POST + @Path("/order/cancel-all") + @Consumes(MediaType.APPLICATION_JSON) + BybitResult cancelAllOrders( + @HeaderParam(X_BAPI_API_KEY) String apiKey, + @HeaderParam(X_BAPI_SIGN) ParamsDigest signature, + @HeaderParam(X_BAPI_TIMESTAMP) SynchronizedValueFactory timestamp, + BybitCancelAllOrdersPayload payload) throws IOException, BybitException; } 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 901b398c3c2..df6969a9ddf 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 @@ -13,25 +13,31 @@ 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.client.ResilienceRegistries; import org.knowm.xchange.exceptions.ExchangeException; -public class BybitExchange extends BaseExchange { +public class BybitExchange extends BaseExchange implements Exchange{ public static final String SPECIFIC_PARAM_ACCOUNT_TYPE = "accountType"; private static final String BASE_URL = "https://api.bybit.com"; - // private static final String DEMO_URL = "https://api-demo.bybit.com"; + private static final String DEMO_URL = "https://api-demo.bybit.com"; private static final String TESTNET_URL = "https://api-testnet.bybit.com"; + // enable DEMO mode + public static final String SPECIFIC_PARAM_TESTNET = "test_net"; + + private static ResilienceRegistries RESILIENCE_REGISTRIES; + @Override protected void initServices() { - marketDataService = new BybitMarketDataService(this); - tradeService = new BybitTradeService(this); + marketDataService = new BybitMarketDataService(this,getResilienceRegistries()); + tradeService = new BybitTradeService(this,getResilienceRegistries()); accountService = new BybitAccountService( this, (BybitAccountType) getExchangeSpecification() - .getExchangeSpecificParametersItem(SPECIFIC_PARAM_ACCOUNT_TYPE)); + .getExchangeSpecificParametersItem(SPECIFIC_PARAM_ACCOUNT_TYPE),getResilienceRegistries()); } @Override @@ -45,6 +51,8 @@ public ExchangeSpecification getDefaultExchangeSpecification() { exchangeSpecification.setExchangeSpecificParametersItem( SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); exchangeSpecification.setExchangeSpecificParametersItem(Exchange.USE_SANDBOX, false); + exchangeSpecification.setExchangeSpecificParametersItem(SPECIFIC_PARAM_TESTNET, false); + exchangeSpecification.getResilience().setRateLimiterEnabled(true); return exchangeSpecification; } @@ -108,8 +116,23 @@ public void applySpecification(ExchangeSpecification exchangeSpecification) { if (exchangeSpecification .getExchangeSpecificParametersItem(Exchange.USE_SANDBOX) .equals(true)) { + exchangeSpecification.setSslUri(DEMO_URL); + } + + if (exchangeSpecification.getExchangeSpecificParametersItem(SPECIFIC_PARAM_TESTNET) != null + && exchangeSpecification + .getExchangeSpecificParametersItem(SPECIFIC_PARAM_TESTNET) + .equals(true)) { exchangeSpecification.setSslUri(TESTNET_URL); } super.applySpecification(exchangeSpecification); } + + @Override + public ResilienceRegistries getResilienceRegistries() { + if (RESILIENCE_REGISTRIES == null) { + RESILIENCE_REGISTRIES = BybitResilience.createRegistries(); + } + return RESILIENCE_REGISTRIES; + } } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitResilience.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitResilience.java new file mode 100644 index 00000000000..ac58e3f7920 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/BybitResilience.java @@ -0,0 +1,161 @@ +package org.knowm.xchange.bybit; + +import static jakarta.ws.rs.core.Response.Status.FORBIDDEN; +import static jakarta.ws.rs.core.Response.Status.TOO_MANY_REQUESTS; + +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import java.time.Duration; +import org.knowm.xchange.client.ResilienceRegistries; +import org.knowm.xchange.client.ResilienceUtils; + +public class BybitResilience { + + /** + * for UTA 2.0 pro account + * Documentation + */ + public static final String GLOBAL_RATE_LIMITER = "global"; + + // /v5/order/create + public static final String ORDER_CREATE_LINEAR_AND_INVERSE_RATE_LIMITER = "orderCreateLinearAndInverse"; + public static final String ORDER_CREATE_SPOT_RATE_LIMITER = "orderCreateSpot"; + public static final String ORDER_CREATE_OPTION_LIMITER = "orderCreateOption"; + + // /v5/order/amend + public static final String ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER = "orderAmendLinearAndInverse"; + public static final String ORDER_AMEND_SPOT_RATE_LIMITER = "orderAmendSpot"; + public static final String ORDER_AMEND_OPTION_LIMITER = "orderAmendOption"; + + // /v5/order/cancel + public static final String ORDER_CANCEL_LINEAR_AND_INVERSE_RATE_LIMITER = "orderCancelLinearAndInverse"; + public static final String ORDER_CANCEL_SPOT_RATE_LIMITER = "orderCancelSpot"; + public static final String ORDER_CANCEL_OPTION_LIMITER = "orderCancelOption"; + + // /v5/position/set-leverage + public static final String POSITION_SET_LEVERAGE_INVERSE_RATE_LIMITER = "positionSetLeverageInverse"; + public static final String POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER = "positionSetLeverageLinear"; + + + public static ResilienceRegistries createRegistries() { + ResilienceRegistries registries = new ResilienceRegistries(); + + registries + .rateLimiters() + .rateLimiter( + GLOBAL_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(5)) + .limitForPeriod(600) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + + // /order/create + registries + .rateLimiters() + .rateLimiter( + ORDER_CREATE_LINEAR_AND_INVERSE_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + ORDER_CREATE_SPOT_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(20) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + ORDER_CREATE_OPTION_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + + // /order/amend + registries + .rateLimiters() + .rateLimiter( + ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + ORDER_AMEND_SPOT_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + ORDER_AMEND_OPTION_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + + // /order/cancel + registries + .rateLimiters() + .rateLimiter( + ORDER_CANCEL_LINEAR_AND_INVERSE_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + ORDER_CANCEL_SPOT_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(20) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + ORDER_CANCEL_OPTION_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + + // /position/set-leverage + registries + .rateLimiters() + .rateLimiter( + POSITION_SET_LEVERAGE_INVERSE_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + registries + .rateLimiters() + .rateLimiter( + POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER, + RateLimiterConfig.from(registries.rateLimiters().getDefaultConfig()) + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(10) + .timeoutDuration(Duration.ofSeconds(1)) + .build()); + return registries; + } + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitAccountInfoResponse.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitAccountInfoResponse.java new file mode 100644 index 00000000000..6348aca182c --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitAccountInfoResponse.java @@ -0,0 +1,17 @@ +package org.knowm.xchange.bybit.dto.account; + + +import lombok.Getter; + +@Getter +public class BybitAccountInfoResponse { + private int unifiedMarginStatus; + private String marginMode; + private boolean isMasterTrader; + private String spotHedgingStatus; + private String updatedTime; + private String dcpStatus; + private int timeWindow; + private int smpGroup; + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersPayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersPayload.java new file mode 100644 index 00000000000..956d0fff149 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersPayload.java @@ -0,0 +1,26 @@ +package org.knowm.xchange.bybit.dto.account; + +import lombok.Getter; +import lombok.Setter; +import org.knowm.xchange.bybit.dto.BybitCategory; + +@Getter +@Setter +public class BybitCancelAllOrdersPayload { + String category; + String symbol; + String baseCoin; + String settleCoin; + String orderFilter; + String stopOrderType; + + public BybitCancelAllOrdersPayload(String category, String symbol, String baseCoin, + String settleCoin, String orderFilter, String stopOrderType) { + this.category = category; + this.symbol = symbol; + this.baseCoin = baseCoin; + this.settleCoin = settleCoin; + this.orderFilter = orderFilter; + this.stopOrderType = stopOrderType; + } +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersResponse.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersResponse.java new file mode 100644 index 00000000000..83eeee9fff8 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/BybitCancelAllOrdersResponse.java @@ -0,0 +1,18 @@ +package org.knowm.xchange.bybit.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Getter; +import lombok.Setter; +import org.knowm.xchange.bybit.dto.trade.BybitOrderResponse; + + +@Getter +@Setter +public class BybitCancelAllOrdersResponse { + + @JsonProperty("list") + List list; + String success; + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSetLeveragePayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSetLeveragePayload.java new file mode 100644 index 00000000000..99f73f22a5d --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSetLeveragePayload.java @@ -0,0 +1,13 @@ +package org.knowm.xchange.bybit.dto.account.position; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class BybitSetLeveragePayload { + private String category; + private String symbol; + private String buyLeverage; + private String sellLeverage; +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSwitchModePayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSwitchModePayload.java new file mode 100644 index 00000000000..2d9e0bcd704 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/account/position/BybitSwitchModePayload.java @@ -0,0 +1,13 @@ +package org.knowm.xchange.bybit.dto.account.position; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public class BybitSwitchModePayload { + private String category; + private String symbol; + private String coin; + private int mode; +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitCancelOrderPayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitCancelOrderPayload.java index 601a78107c8..d3e481ca180 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitCancelOrderPayload.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitCancelOrderPayload.java @@ -6,10 +6,10 @@ @Getter public class BybitCancelOrderPayload { - private BybitCategory category; - private String symbol; - private String orderId; - private String orderLinkId; + private final BybitCategory category; + private final String symbol; + private final String orderId; + private final String orderLinkId; private String orderFilter; public BybitCancelOrderPayload( diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOpenOrdersPayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOpenOrdersPayload.java new file mode 100644 index 00000000000..e31e3392b73 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOpenOrdersPayload.java @@ -0,0 +1,11 @@ +package org.knowm.xchange.bybit.dto.trade; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class BybitOpenOrdersPayload { + private final String category; + private final String orderId; +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOrder.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOrder.java new file mode 100644 index 00000000000..ff8fea1f00c --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitOrder.java @@ -0,0 +1,109 @@ +package org.knowm.xchange.bybit.dto.trade; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.math.BigDecimal; +import java.util.Date; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.instrument.Instrument; + +@Getter +@Setter +public class BybitOrder extends Order { + + private BigDecimal sLTriggerPrice; + private SlTriggerBy slTriggerBy; + private BigDecimal slLimitPrice; + private BybitOrderType slOrderType; + private BybitOrderType orderType; + private boolean reduceOnly = false; + /** + *

0: one-way mode

+ *

1: hedge-mode Buy side

+ *

2: hedge-mode Sell side

+ */ + private int positionIdx; + private BybitTimeInForce timeInForce; + + public BybitOrder( + OrderType type, + BybitOrderType orderType, + String userId, + Instrument instrument, + BigDecimal amount, + BigDecimal price, + Date timestamp, + BigDecimal sLTriggerPrice, + SlTriggerBy slTriggerBy, + BigDecimal slLimitPrice, + BybitOrderType slOrderType, + BigDecimal fee, + BybitTimeInForce timeInForce, + boolean reduceOnly, + int positionIdx) { + super( + type, + amount, + instrument, + "", + timestamp, + price, + amount, + fee, + OrderStatus.PENDING_NEW, + userId); + this.orderType = orderType; + this.sLTriggerPrice = sLTriggerPrice; + this.slTriggerBy = slTriggerBy; + this.slLimitPrice = slLimitPrice; + this.slOrderType = slOrderType; + this.timeInForce = timeInForce; + this.reduceOnly = reduceOnly; + this.positionIdx = positionIdx; + } + + public BybitOrder( + OrderType type, + BybitOrderType orderType, + String userId, + Instrument instrument, + BigDecimal amount, + BigDecimal price, + Date timestamp) { + super( + type, + amount, + instrument, + "", + timestamp, + price, + amount, + null, + OrderStatus.PENDING_NEW, + userId); + this.orderType = orderType; + } + + @Getter + @AllArgsConstructor + public enum SlTriggerBy { + LASTPRICE("LastPrice"), + MARKPRICE("MarkPrice"), + INDEXPRICE("IndexPrice"); + @JsonValue + private final String value; + } + + @Getter + @AllArgsConstructor + public enum TpslMode { + FULL("Full"), + PARTIAL("Partial"); + @JsonValue + private final String value; + } + +} diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitPlaceOrderPayload.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitPlaceOrderPayload.java index 4bf30322d2a..5bb1c67cfd1 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitPlaceOrderPayload.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/BybitPlaceOrderPayload.java @@ -2,47 +2,106 @@ import java.math.BigDecimal; import lombok.Getter; +import lombok.Setter; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.trade.BybitOrder.SlTriggerBy; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; @Getter +@Setter public class BybitPlaceOrderPayload { - private String category; - private String symbol; - private String side; - private String orderType; + private final String category; + private final String symbol; + private final String side; + private final String orderType; private String qty; private String orderLinkId; private String price; + private String stopLoss; + private String slTriggerBy; + private String slLimitPrice; + private String slOrderType; + private String tpslMode; + private String reduceOnly; + private int positionIdx; + private String timeInForce; public BybitPlaceOrderPayload( - String category, + BybitCategory category, String symbol, - String side, - String orderType, + BybitSide side, + BybitOrderType orderType, BigDecimal qty, String orderLinkId) { - this.category = category; + this.category = category.getValue(); this.symbol = symbol; - this.side = side; - this.orderType = orderType; + this.side = side.getValue(); + this.orderType = orderType.getValue(); this.qty = qty.toString(); this.orderLinkId = orderLinkId; } public BybitPlaceOrderPayload( - String category, + BybitCategory category, String symbol, - String side, - String orderType, + BybitSide side, + BybitOrderType orderType, BigDecimal qty, String orderLinkId, + int positionIdx, BigDecimal price) { - this.category = category; + this.category = category.getValue(); this.symbol = symbol; - this.side = side; - this.orderType = orderType; + this.side = side.getValue(); + this.orderType = orderType.getValue(); + this.qty = qty.toString(); + this.orderLinkId = orderLinkId; + this.positionIdx = positionIdx; + if(price != null) { + this.price = price.toString(); + } + } + + public BybitPlaceOrderPayload( + BybitCategory category, + String symbol, + BybitSide side, + BybitOrderType orderType, + BigDecimal qty, + String orderLinkId, + BigDecimal price) { + this.category = category.getValue(); + this.symbol = symbol; + this.side = side.getValue(); + this.orderType = orderType.getValue(); + this.qty = qty.toString(); + this.orderLinkId = orderLinkId; + this.price = price.toString(); + } + + public BybitPlaceOrderPayload( + BybitCategory category, + String symbol, + BybitSide side, + BybitOrderType orderType, + BigDecimal qty, + String orderLinkId, + BigDecimal price, + BigDecimal stopLoss, + SlTriggerBy slTriggerBy, + BigDecimal slLimitPrice, + BybitOrderType slOrderType) { + this.category = category.getValue(); + this.symbol = symbol; + this.side = side.getValue(); + this.orderType = orderType.getValue(); this.qty = qty.toString(); this.orderLinkId = orderLinkId; this.price = price.toString(); + this.stopLoss = stopLoss.toString(); + this.slTriggerBy = slTriggerBy.getValue(); + this.slLimitPrice = slLimitPrice.toString(); + this.slOrderType = slOrderType.getValue(); } } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitHedgeMode.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitHedgeMode.java new file mode 100644 index 00000000000..90b0e136950 --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitHedgeMode.java @@ -0,0 +1,14 @@ +package org.knowm.xchange.bybit.dto.trade.details; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.knowm.xchange.dto.Order.IOrderFlags; + + @Getter + @AllArgsConstructor + public enum BybitHedgeMode implements IOrderFlags { + ONEWAY, + TWOWAY; + } + + diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitTimeInForce.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitTimeInForce.java new file mode 100644 index 00000000000..4f61451b2fb --- /dev/null +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/dto/trade/details/BybitTimeInForce.java @@ -0,0 +1,19 @@ +package org.knowm.xchange.bybit.dto.trade.details; + +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.knowm.xchange.dto.Order.IOrderFlags; + +@Getter +@AllArgsConstructor +public enum BybitTimeInForce implements IOrderFlags { + GTC("GTC"), + IOC("IOC"), + FOK("FOK"), + POSTONLY("PostOnly"); + @JsonValue + private final String value; +} + + diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountService.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountService.java index 187199eacc1..f1213d61a91 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountService.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountService.java @@ -1,27 +1,35 @@ package org.knowm.xchange.bybit.service; import static org.knowm.xchange.bybit.BybitAdapters.adaptBybitBalances; +import static org.knowm.xchange.bybit.BybitAdapters.convertToBybitSymbol; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import org.knowm.xchange.Exchange; +import org.knowm.xchange.bybit.BybitAdapters; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.account.BybitAccountInfoResponse; import org.knowm.xchange.bybit.dto.account.allcoins.BybitAllCoinsBalance; +import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRates; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountBalance; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitWalletBalance; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.dto.account.AccountInfo; import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.account.AccountService; public class BybitAccountService extends BybitAccountServiceRaw implements AccountService { private final BybitAccountType accountType; - public BybitAccountService(Exchange exchange, BybitAccountType accountType) { - super(exchange); + public BybitAccountService(BybitExchange exchange, BybitAccountType accountType, + ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); this.accountType = accountType; } @@ -31,6 +39,36 @@ public AccountInfo getAccountInfo() throws IOException { return new AccountInfo(adaptedWallets); } + /** + * According to the risk limit, leverage affects the maximum position value that can be opened, + * that is, the greater the leverage, the smaller the maximum position value that can be opened, + * and vice versa + * + * @return true, if success + */ + public boolean setLeverage(Instrument instrument, double leverage) + throws IOException { + BybitCategory category = BybitAdapters.getCategory(instrument); + int retCode = + setLeverageRaw(category, convertToBybitSymbol(instrument), leverage) + .getRetCode(); + return retCode == 0 || retCode == 110043; + } + + /** + * @param mode 0: Merged Single. 3: Both Sides + * @throws IOException + */ + public boolean switchPositionMode(BybitCategory category, Instrument instrument, String coin, int mode) + throws IOException { + String symbol = ""; + if (instrument != null) { + symbol = BybitAdapters.convertToBybitSymbol(instrument); + } + int retCode = switchPositionModeRaw(category, symbol, coin, mode).getRetCode(); + return retCode == 0 || retCode == 110025; + } + private List getAdaptedWallets() throws IOException { switch (accountType) { case CONTRACT: @@ -62,4 +100,20 @@ private List getAdaptedBalanceWallets() throws IOException { .map(bybitAccountBalance -> adaptBybitBalances(bybitAccountBalance.getCoin())) .collect(Collectors.toList()); } + + public BybitResult getFeeRates(BybitCategory category, Instrument instrument) + throws IOException { + String symbol = ""; + if (instrument != null) { + symbol = BybitAdapters.convertToBybitSymbol(instrument); + } + return getFeeRatesRaw(category, symbol); + } + + /** + * Query the account information, like margin mode, account mode, etc. + */ + public BybitAccountInfoResponse accountInfo() throws IOException { + return accountInfoRaw().getResult(); + } } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountServiceRaw.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountServiceRaw.java index 190e57d5a24..d3b41816b16 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountServiceRaw.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitAccountServiceRaw.java @@ -1,23 +1,31 @@ package org.knowm.xchange.bybit.service; import static org.knowm.xchange.bybit.BybitAdapters.createBybitExceptionFromResult; +import static org.knowm.xchange.bybit.BybitResilience.GLOBAL_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.POSITION_SET_LEVERAGE_INVERSE_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER; +import io.github.resilience4j.ratelimiter.RateLimiter; import java.io.IOException; -import org.knowm.xchange.Exchange; +import org.knowm.xchange.bybit.BybitExchange; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.account.BybitAccountInfoResponse; import org.knowm.xchange.bybit.dto.account.allcoins.BybitAllCoinsBalance; import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRates; +import org.knowm.xchange.bybit.dto.account.position.BybitSetLeveragePayload; +import org.knowm.xchange.bybit.dto.account.position.BybitSwitchModePayload; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitWalletBalance; +import org.knowm.xchange.client.ResilienceRegistries; public class BybitAccountServiceRaw extends BybitBaseService { - public BybitAccountServiceRaw(Exchange exchange) { - super(exchange); + protected BybitAccountServiceRaw(BybitExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } - public BybitResult getWalletBalances(BybitAccountType accountType) + BybitResult getWalletBalances(BybitAccountType accountType) throws IOException { BybitResult walletBalances = bybitAuthenticated.getWalletBalance( @@ -28,7 +36,7 @@ public BybitResult getWalletBalances(BybitAccountType accoun return walletBalances; } - public BybitResult getAllCoinsBalance(BybitAccountType accountType) + BybitResult getAllCoinsBalance(BybitAccountType accountType) throws IOException { BybitResult allCoinsBalance = bybitAuthenticated.getAllCoinsBalance( @@ -39,7 +47,7 @@ public BybitResult getAllCoinsBalance(BybitAccountType acc return allCoinsBalance; } - public BybitResult getFeeRates(BybitCategory category, String symbol) + BybitResult getFeeRatesRaw(BybitCategory category, String symbol) throws IOException { BybitResult bybitFeeRatesResult = bybitAuthenticated.getFeeRates( @@ -49,4 +57,57 @@ public BybitResult getFeeRates(BybitCategory category, String sym } return bybitFeeRatesResult; } + + BybitResult setLeverageRaw(BybitCategory category, String symbol, double leverage) + throws IOException { + String leverageString = Double.toString(leverage); + BybitSetLeveragePayload payload = + new BybitSetLeveragePayload(category.getValue(), symbol, leverageString, leverageString); + RateLimiter rateLimiter = null; + switch (category) { + case INVERSE: + rateLimiter = rateLimiter(POSITION_SET_LEVERAGE_INVERSE_RATE_LIMITER); + break; + case LINEAR: + rateLimiter = rateLimiter(POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER); + break; + default: + throw new UnsupportedOperationException("Only Linear and Inverse category"); + } + BybitResult setLeverageResult = + decorateApiCall(() -> + bybitAuthenticated.setLeverage(apiKey, signatureCreator, nonceFactory, payload)) + .withRateLimiter(rateLimiter) + .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) + .call(); + // retCode=110043, retMsg=leverage not modified - also is success + if (!setLeverageResult.isSuccess() && setLeverageResult.getRetCode() != 110043) { + throw createBybitExceptionFromResult(setLeverageResult); + } + return setLeverageResult; + } + + BybitResult switchPositionModeRaw( + BybitCategory category, String symbol, String coin, int mode) throws IOException { + BybitSwitchModePayload payload = + new BybitSwitchModePayload(category.getValue(), symbol, coin, mode); + BybitResult switchModeResult = + bybitAuthenticated.switchMode(apiKey, signatureCreator, nonceFactory, payload); + // retCode=110025, retMsg=Position mode is not modified - also is success + if (!switchModeResult.isSuccess() && switchModeResult.getRetCode() != 110025) { + throw createBybitExceptionFromResult(switchModeResult); + } + return switchModeResult; + } + + BybitResult accountInfoRaw() + throws IOException { + BybitResult accountInfo = + bybitAuthenticated.getAccountInfo(apiKey, signatureCreator, nonceFactory); + if (!accountInfo.isSuccess()) { + throw createBybitExceptionFromResult(accountInfo); + } + return accountInfo; + } + } 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 ba709651571..7fa0ceaa7a4 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,13 +4,17 @@ 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.client.ExchangeRestProxyBuilder; +import org.knowm.xchange.client.ResilienceRegistries; +import org.knowm.xchange.service.BaseExchangeService; +import org.knowm.xchange.service.BaseResilientExchangeService; import org.knowm.xchange.service.BaseService; import org.knowm.xchange.utils.nonce.CurrentTimeIncrementalNonceFactory; import si.mazi.rescu.ParamsDigest; import si.mazi.rescu.SynchronizedValueFactory; -public class BybitBaseService implements BaseService { +public class BybitBaseService extends BaseResilientExchangeService { protected final BybitAuthenticated bybitAuthenticated; protected final Bybit bybit; @@ -19,7 +23,8 @@ public class BybitBaseService implements BaseService { new CurrentTimeIncrementalNonceFactory(TimeUnit.MILLISECONDS); protected final String apiKey; - public BybitBaseService(Exchange exchange) { + protected BybitBaseService(BybitExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); bybit = ExchangeRestProxyBuilder.forInterface(Bybit.class, exchange.getExchangeSpecification()) .clientConfigCustomizer( 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 fdd09b42a1a..ac5d2c8497f 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 @@ -12,6 +12,7 @@ import org.knowm.xchange.bybit.dto.marketdata.tickers.linear.BybitLinearInverseTicker; import org.knowm.xchange.bybit.dto.marketdata.tickers.option.BybitOptionTicker; import org.knowm.xchange.bybit.dto.marketdata.tickers.spot.BybitSpotTicker; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.currency.CurrencyPair; import org.knowm.xchange.dto.marketdata.Ticker; import org.knowm.xchange.exceptions.NotYetImplementedForExchangeException; @@ -22,8 +23,8 @@ public class BybitMarketDataService extends BybitMarketDataServiceRaw implements MarketDataService { - public BybitMarketDataService(BybitExchange exchange) { - super(exchange); + public BybitMarketDataService(BybitExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange,resilienceRegistries); } @Override 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 c64913b466c..bfc1360db02 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 @@ -9,11 +9,12 @@ import org.knowm.xchange.bybit.dto.marketdata.instruments.BybitInstrumentsInfo; import org.knowm.xchange.bybit.dto.marketdata.tickers.BybitTicker; import org.knowm.xchange.bybit.dto.marketdata.tickers.BybitTickers; +import org.knowm.xchange.client.ResilienceRegistries; public class BybitMarketDataServiceRaw extends BybitBaseService { - public BybitMarketDataServiceRaw(BybitExchange exchange) { - super(exchange); + public BybitMarketDataServiceRaw(BybitExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange,resilienceRegistries); } public BybitResult> getTicker24h(BybitCategory category, String symbol) diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeService.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeService.java index 37a0d069009..4b49675143a 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeService.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeService.java @@ -2,56 +2,111 @@ import static org.knowm.xchange.bybit.BybitAdapters.adaptBybitOrderDetails; import static org.knowm.xchange.bybit.BybitAdapters.convertToBybitSymbol; +import static org.knowm.xchange.bybit.BybitAdapters.createBybitExceptionFromResult; +import static org.knowm.xchange.bybit.dto.trade.details.BybitHedgeMode.TWOWAY; +import static org.knowm.xchange.dto.Order.OrderType.ASK; +import static org.knowm.xchange.dto.Order.OrderType.BID; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.knowm.xchange.Exchange; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.Getter; import org.knowm.xchange.bybit.BybitAdapters; +import org.knowm.xchange.bybit.BybitExchange; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.account.BybitCancelAllOrdersResponse; + import org.knowm.xchange.bybit.dto.trade.BybitOrderResponse; +import org.knowm.xchange.bybit.dto.trade.BybitOrderType; +import org.knowm.xchange.bybit.dto.trade.details.BybitHedgeMode; import org.knowm.xchange.bybit.dto.trade.details.BybitOrderDetail; import org.knowm.xchange.bybit.dto.trade.details.BybitOrderDetails; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; +import org.knowm.xchange.client.ResilienceRegistries; import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.Order.IOrderFlags; +import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.instrument.Instrument; import org.knowm.xchange.service.trade.TradeService; +import org.knowm.xchange.service.trade.params.CancelAllOrders; +import org.knowm.xchange.service.trade.params.CancelOrderByIdParams; +import org.knowm.xchange.service.trade.params.CancelOrderByInstrument; +import org.knowm.xchange.service.trade.params.CancelOrderByUserReferenceParams; +import org.knowm.xchange.service.trade.params.CancelOrderParams; +import org.knowm.xchange.service.trade.params.DefaultCancelOrderByInstrumentAndIdParams; public class BybitTradeService extends BybitTradeServiceRaw implements TradeService { - public BybitTradeService(Exchange exchange) { - super(exchange); + public BybitTradeService(BybitExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } @Override public String placeMarketOrder(MarketOrder marketOrder) throws IOException { + BybitCategory category = BybitAdapters.getCategory(marketOrder.getInstrument()); + int positionIdx = getPositionIdx(marketOrder); + boolean reduceOnly = + marketOrder.getType().equals(OrderType.EXIT_ASK) + || marketOrder.getType().equals(OrderType.EXIT_BID); BybitResult orderResponseBybitResult = - placeMarketOrder( - BybitAdapters.getCategory(marketOrder.getInstrument()), - BybitAdapters.convertToBybitSymbol(marketOrder.getInstrument()), + placeOrder( + category, + convertToBybitSymbol(marketOrder.getInstrument()), BybitAdapters.getSideString(marketOrder.getType()), + BybitOrderType.MARKET, marketOrder.getOriginalAmount(), - marketOrder.getUserReference()); - + null, + marketOrder.getUserReference(), + null, + null, + null, + null, + reduceOnly, + positionIdx, + BybitTimeInForce.IOC); return orderResponseBybitResult.getResult().getOrderId(); } @Override public String placeLimitOrder(LimitOrder limitOrder) throws IOException { + BybitCategory category = BybitAdapters.getCategory(limitOrder.getInstrument()); + BybitTimeInForce timeInForce = getOrderFlag(limitOrder, BybitTimeInForce.class).orElse(BybitTimeInForce.GTC); + int positionIdx = getPositionIdx(limitOrder); + boolean reduceOnly = + limitOrder.getType().equals(OrderType.EXIT_ASK) + || limitOrder.getType().equals(OrderType.EXIT_BID); BybitResult orderResponseBybitResult = - placeLimitOrder( - BybitAdapters.getCategory(limitOrder.getInstrument()), - BybitAdapters.convertToBybitSymbol(limitOrder.getInstrument()), + placeOrder( + category, + convertToBybitSymbol(limitOrder.getInstrument()), BybitAdapters.getSideString(limitOrder.getType()), + BybitOrderType.LIMIT, limitOrder.getOriginalAmount(), limitOrder.getLimitPrice(), - limitOrder.getUserReference()); - + limitOrder.getUserReference(), + null, + null, + null, + null, + reduceOnly, + positionIdx, + timeInForce); return orderResponseBybitResult.getResult().getOrderId(); } + private Optional getOrderFlag(Order order, Class clazz) { + return (Optional) + order.getOrderFlags().stream() + .filter(flag -> clazz.isAssignableFrom(flag.getClass())) + .findFirst(); + } + @Override public Collection getOrder(String... orderIds) throws IOException { List results = new ArrayList<>(); @@ -63,56 +118,198 @@ public Collection getOrder(String... orderIds) throws IOException { getBybitOrder(category, orderId); if (bybitOrder.getResult().getCategory().equals(category) - && bybitOrder.getResult().getList().size() > 0) { + && !bybitOrder.getResult().getList().isEmpty()) { BybitOrderDetail bybitOrderDetail = bybitOrder.getResult().getList().get(0); Order order = adaptBybitOrderDetails(bybitOrderDetail); results.add(order); } } } - return results; } - public String amendOrder(Order order) throws IOException { + @Override + public String changeOrder(LimitOrder order) throws IOException { BybitCategory category = BybitAdapters.getCategory(order.getInstrument()); - BybitResult response = null; - if (order instanceof LimitOrder) { - response = - amendOrder( - category, - convertToBybitSymbol(order.getInstrument()), - order.getId(), - order.getUserReference(), - null, - order.getOriginalAmount().toString(), - ((LimitOrder) order).getLimitPrice().toString(), - null, - null, - null, - null, - null, - null, - null, - null); - } - // Todo order instanceof StopOrder + BybitResult response = amendOrder( + category, + convertToBybitSymbol(order.getInstrument()), + order.getId(), + order.getUserReference(), + null, + order.getOriginalAmount().toString(), + order.getLimitPrice().toString(), + null, + null, + null, + null, + null, + null, + null, + null); if (response != null) { return response.getResult().getOrderId(); } return ""; } - public String cancelOrder(Order order) throws IOException { - BybitCategory category = BybitAdapters.getCategory(order.getInstrument()); - BybitResult response = - cancelOrder( - category, - convertToBybitSymbol(order.getInstrument()), - order.getId(), - order.getUserReference()); - if (response != null) { - return response.getResult().getOrderId(); - } else return ""; + @Override + public Class[] getRequiredCancelOrderParamClasses() { + return new Class[]{CancelOrderByIdParams.class, CancelOrderByInstrument.class, + CancelOrderByUserReferenceParams.class}; } + + @Override + public boolean cancelOrder(CancelOrderParams params) throws IOException { + if (params instanceof BybitCancelOrderParams) { + Instrument instrument = ((BybitCancelOrderParams) params).getInstrument(); + BybitCategory category = BybitAdapters.getCategory(instrument); + String orderId = ((BybitCancelOrderParams) params).getOrderId(); + String userReference = ((BybitCancelOrderParams) params).getUserReference(); + if (instrument == null) { + throw new UnsupportedOperationException( + "Instrument and (orderId or userReference) required"); + } + if ((orderId == null || orderId.isEmpty()) && (userReference == null + || userReference.isEmpty())) { + throw new UnsupportedOperationException("OrderId or userReference is required"); + } + BybitResult response = + cancelOrder( + category, + convertToBybitSymbol(instrument), + orderId, + userReference); + if (!response.isSuccess()) { + throw createBybitExceptionFromResult(response); + } + return true; + } else { + throw new UnsupportedOperationException("Params must be instance of BybitCancelOrderParams"); + + } + } + + @Override + public Collection cancelAllOrders(CancelAllOrders params) throws IOException { + if (params instanceof BybitCancelAllOrdersParams) { + Instrument instrument = ((BybitCancelAllOrdersParams) params).getSymbol(); + BybitCategory category = ((BybitCancelAllOrdersParams) params).getCategory(); + String baseCoin = ((BybitCancelAllOrdersParams) params).getBaseCoin(); + String settleCoin = ((BybitCancelAllOrdersParams) params).getSettleCoin(); + String orderFilter = ((BybitCancelAllOrdersParams) params).getOrderFilter(); + String stopOrderType = ((BybitCancelAllOrdersParams) params).getStopOrderType(); + if (category == null) { + throw new UnsupportedOperationException("Category is required"); + } + String symbol = ""; + BybitResult response; + switch (category) { + case SPOT: + case OPTION: + break; + case LINEAR: + case INVERSE: { + if (instrument != null) { + symbol = convertToBybitSymbol(instrument); + } else if (baseCoin == null && settleCoin == null) { + throw new UnsupportedOperationException( + "For Linear or Inverse category, required instrument or baseCoin or settleCoin"); + } + } + } + + response = + cancelAllOrders(category.getValue(), symbol, + baseCoin, settleCoin, orderFilter, stopOrderType); + if (response != null) { + return response.getResult().getList().stream().map(BybitOrderResponse::getOrderId).collect( + Collectors.toList()); + } + } else { + throw new UnsupportedOperationException( + "Params must be instance of BybitCancelAllOrdersParams"); + } + return null; + } + + private int getPositionIdx(Order order) { + BybitHedgeMode hedgeMode = getOrderFlag(order, BybitHedgeMode.class).orElse(BybitHedgeMode.ONEWAY); + int positionIdx = 0; + if (hedgeMode.equals(TWOWAY)) { + positionIdx = 1; + switch (order.getType()) { + case ASK: + case EXIT_ASK: { + positionIdx = 2; + break; + } + case BID: + case EXIT_BID: { + break; + } + } + } + return positionIdx; + } + + @Getter + public static final class BybitCancelOrderParams extends DefaultCancelOrderByInstrumentAndIdParams + implements CancelOrderByUserReferenceParams { + + private final String userReference; + + public BybitCancelOrderParams(Instrument instrument, String orderId, String userReference) { + super(instrument, orderId); + this.userReference = userReference; + } + + @Override + public String toString() { + return "BybitCancelOrderParams{" + + "instrument='" + getInstrument() + '\'' + + ", orderId='" + getOrderId() + '\'' + + ", userReference=" + getUserReference() + + '}'; + } + } + + @Getter + public static class BybitCancelAllOrdersParams implements CancelAllOrders { + + private final BybitCategory category; + private final Instrument symbol; + private String baseCoin; + private String settleCoin; + private String orderFilter; + private String stopOrderType; + + public BybitCancelAllOrdersParams(BybitCategory category, Instrument symbol) { + this.category = category; + this.symbol = symbol; + } + + public BybitCancelAllOrdersParams(BybitCategory category, Instrument symbol, String baseCoin, + String settleCoin, String orderFilter, String stopOrderType) { + this.category = category; + this.symbol = symbol; + this.baseCoin = baseCoin; + this.settleCoin = settleCoin; + this.orderFilter = orderFilter; + this.stopOrderType = stopOrderType; + } + + @Override + public String toString() { + return "BybitCancelAllOrdersParams{" + + "category=" + category + + ", symbol=" + symbol + + ", baseCoin='" + baseCoin + '\'' + + ", settleCoin='" + settleCoin + '\'' + + ", orderFilter='" + orderFilter + '\'' + + ", stopOrderType='" + stopOrderType + '\'' + + '}'; + } + } + } diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java index 3eba5bd9b9b..b972dafa8bf 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java @@ -1,12 +1,31 @@ package org.knowm.xchange.bybit.service; import static org.knowm.xchange.bybit.BybitAdapters.createBybitExceptionFromResult; +import static org.knowm.xchange.bybit.BybitResilience.GLOBAL_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_AMEND_OPTION_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_AMEND_SPOT_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_CANCEL_LINEAR_AND_INVERSE_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_CANCEL_OPTION_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_CANCEL_SPOT_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_CREATE_LINEAR_AND_INVERSE_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_CREATE_OPTION_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_CREATE_SPOT_RATE_LIMITER; +import static org.knowm.xchange.bybit.dto.trade.BybitOrder.TpslMode.FULL; +import static org.knowm.xchange.bybit.dto.trade.BybitOrder.TpslMode.PARTIAL; +import static org.knowm.xchange.bybit.dto.trade.BybitOrderType.MARKET; +import io.github.resilience4j.ratelimiter.RateLimiter; import java.io.IOException; import java.math.BigDecimal; -import org.knowm.xchange.Exchange; +import org.knowm.xchange.bybit.BybitExchange; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.account.BybitCancelAllOrdersPayload; +import org.knowm.xchange.bybit.dto.account.BybitCancelAllOrdersResponse; + +import org.knowm.xchange.bybit.dto.trade.BybitOrder.SlTriggerBy; + import org.knowm.xchange.bybit.dto.trade.BybitAmendOrderPayload; import org.knowm.xchange.bybit.dto.trade.BybitCancelOrderPayload; import org.knowm.xchange.bybit.dto.trade.BybitOrderResponse; @@ -15,14 +34,16 @@ import org.knowm.xchange.bybit.dto.trade.BybitSide; import org.knowm.xchange.bybit.dto.trade.details.BybitOrderDetail; import org.knowm.xchange.bybit.dto.trade.details.BybitOrderDetails; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; +import org.knowm.xchange.client.ResilienceRegistries; public class BybitTradeServiceRaw extends BybitBaseService { - public BybitTradeServiceRaw(Exchange exchange) { - super(exchange); + protected BybitTradeServiceRaw(BybitExchange exchange, ResilienceRegistries resilienceRegistries) { + super(exchange, resilienceRegistries); } - public BybitResult> getBybitOrder( + BybitResult> getBybitOrder( BybitCategory category, String orderId) throws IOException { BybitResult> order = bybitAuthenticated.getOpenOrders( @@ -33,51 +54,51 @@ public BybitResult> getBybitOrder( return order; } - public BybitResult placeMarketOrder( - BybitCategory category, String symbol, BybitSide side, BigDecimal qty, String orderLinkId) - throws IOException { - BybitPlaceOrderPayload payload = - new BybitPlaceOrderPayload( - category.getValue(), - symbol, - side.getValue(), - BybitOrderType.MARKET.getValue(), - qty, - orderLinkId); - BybitResult placeOrder = - bybitAuthenticated.placeMarketOrder(apiKey, signatureCreator, nonceFactory, payload); - if (!placeOrder.isSuccess()) { - throw createBybitExceptionFromResult(placeOrder); - } - return placeOrder; - } +// BybitResult placeMarketOrder( +// BybitCategory category, String symbol, BybitSide side, BigDecimal qty, String orderLinkId) +// throws IOException { +// BybitPlaceOrderPayload payload = +// new BybitPlaceOrderPayload(category, symbol, side, MARKET, qty, orderLinkId); +// BybitResult placeOrder = +// decorateApiCall( +// () -> bybitAuthenticated.placeMarketOrder(apiKey, signatureCreator, nonceFactory, +// payload)) +// .withRateLimiter(getCreateOrderRateLimiter(category)) +// .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) +// .call(); +// if (!placeOrder.isSuccess()) { +// throw createBybitExceptionFromResult(placeOrder); +// } +// return placeOrder; +// } - public BybitResult placeLimitOrder( - BybitCategory category, - String symbol, - BybitSide side, - BigDecimal qty, - BigDecimal limitPrice, - String orderLinkId) - throws IOException { - BybitPlaceOrderPayload payload = - new BybitPlaceOrderPayload( - category.getValue(), - symbol, - side.getValue(), - BybitOrderType.LIMIT.getValue(), - qty, - orderLinkId, - limitPrice); - BybitResult placeOrder = - bybitAuthenticated.placeLimitOrder(apiKey, signatureCreator, nonceFactory, payload); - if (!placeOrder.isSuccess()) { - throw createBybitExceptionFromResult(placeOrder); - } - return placeOrder; - } +//BybitResult placeLimitOrder( +// BybitCategory category, +// String symbol, +// BybitSide side, +// BigDecimal qty, +// BigDecimal limitPrice, +// String orderLinkId, +// boolean reduceOnly) +// throws IOException { +// BybitPlaceOrderPayload payload = +// new BybitPlaceOrderPayload( +// category, symbol, side, BybitOrderType.LIMIT, qty, orderLinkId, limitPrice); +// payload.setReduceOnly(String.valueOf(reduceOnly)); +// BybitResult placeOrder = +// decorateApiCall( +// () -> bybitAuthenticated.placeLimitOrder(apiKey, signatureCreator, nonceFactory, +// payload)) +// .withRateLimiter(getCreateOrderRateLimiter(category)) +// .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) +// .call(); +// if (!placeOrder.isSuccess()) { +// throw createBybitExceptionFromResult(placeOrder); +// } +// return placeOrder; +// } - public BybitResult amendOrder( + BybitResult amendOrder( BybitCategory category, String symbol, String orderId, @@ -95,7 +116,10 @@ public BybitResult amendOrder( String slLimitPrice) throws IOException { // if only userId is used, don't need to send id - if (orderId != null && orderId.isEmpty()) orderId = null; + if (orderId != null && orderId.isEmpty()) { + orderId = null; + } + RateLimiter rateLimiter = getAmendOrderRateLimiter(category); BybitAmendOrderPayload payload = new BybitAmendOrderPayload( category, @@ -114,23 +138,126 @@ public BybitResult amendOrder( tpLimitPrice, slLimitPrice); BybitResult amendOrder = - bybitAuthenticated.amendOrder(apiKey, signatureCreator, nonceFactory, payload); + decorateApiCall( + () -> bybitAuthenticated.amendOrder(apiKey, signatureCreator, nonceFactory, payload)) + .withRateLimiter(rateLimiter) + .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) + .call(); if (!amendOrder.isSuccess()) { throw createBybitExceptionFromResult(amendOrder); } return amendOrder; } - public BybitResult cancelOrder( + BybitResult placeOrder( + BybitCategory category, + String symbol, + BybitSide side, + BybitOrderType orderType, + BigDecimal qty, + BigDecimal limitPrice, + String orderLinkId, + BigDecimal stopLoss, + SlTriggerBy slTriggerBy, + BigDecimal slLimitPrice, + BybitOrderType slOrderType, + boolean reduceOnly, + int positionIdx, + BybitTimeInForce timeInForce) + throws IOException { + + BybitPlaceOrderPayload payload = + new BybitPlaceOrderPayload( + category, symbol, side, orderType, qty, orderLinkId, positionIdx, limitPrice); + if (stopLoss != null && slTriggerBy != null && slLimitPrice != null && slOrderType != null) { + payload.setStopLoss(stopLoss.toString()); + payload.setSlTriggerBy(slTriggerBy.getValue()); + payload.setSlLimitPrice(slLimitPrice.toString()); + payload.setSlOrderType(slOrderType.getValue()); + if (slOrderType.equals(MARKET)) { + payload.setTpslMode(FULL.getValue()); + } else { + payload.setTpslMode(PARTIAL.getValue()); + } + } + if (reduceOnly) { + payload.setReduceOnly("true"); + } + if (timeInForce != null) { + payload.setTimeInForce(timeInForce.getValue()); + } + BybitResult placeOrder = + decorateApiCall(() -> bybitAuthenticated.placeOrder(apiKey, signatureCreator, nonceFactory, payload)) + .withRateLimiter(getCreateOrderRateLimiter(category)) + .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) + .call(); + if (!placeOrder.isSuccess()) { + throw createBybitExceptionFromResult(placeOrder); + } + return placeOrder; + } + + BybitResult cancelOrder( BybitCategory category, String symbol, String orderId, String orderLinkId) throws IOException { + RateLimiter rateLimiter = getCancelOrderRateLimiter(category); BybitCancelOrderPayload payload = new BybitCancelOrderPayload(category, symbol, orderId, orderLinkId); - BybitResult cancelOrder = - bybitAuthenticated.cancelOrder(apiKey, signatureCreator, nonceFactory, payload); - if (!cancelOrder.isSuccess()) { - throw createBybitExceptionFromResult(cancelOrder); + return decorateApiCall( + () -> bybitAuthenticated.cancelOrder(apiKey, signatureCreator, nonceFactory, payload)) + .withRateLimiter(rateLimiter) + .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) + .call(); + } + + BybitResult cancelAllOrders(String category, String symbol, + String baseCoin, String settleCoin, String orderFilter, String stopOrderType) + throws IOException { + BybitCancelAllOrdersPayload payload = new BybitCancelAllOrdersPayload(category, symbol, + baseCoin, settleCoin, orderFilter, stopOrderType); + BybitResult response = + bybitAuthenticated.cancelAllOrders(apiKey, signatureCreator, nonceFactory, payload); + if (!response.isSuccess()) { + throw createBybitExceptionFromResult(response); + } + return response; + } + + private RateLimiter getCreateOrderRateLimiter(BybitCategory category) { + switch (category) { + case LINEAR: + case INVERSE: + return rateLimiter(ORDER_CREATE_LINEAR_AND_INVERSE_RATE_LIMITER); + case SPOT: + return rateLimiter(ORDER_CREATE_SPOT_RATE_LIMITER); + case OPTION: + return rateLimiter(ORDER_CREATE_OPTION_LIMITER); + } + return null; + } + + private RateLimiter getCancelOrderRateLimiter(BybitCategory category) { + switch (category) { + case LINEAR: + case INVERSE: + return rateLimiter(ORDER_CANCEL_LINEAR_AND_INVERSE_RATE_LIMITER); + case SPOT: + return rateLimiter(ORDER_CANCEL_SPOT_RATE_LIMITER); + case OPTION: + return rateLimiter(ORDER_CANCEL_OPTION_LIMITER); + } + return null; + } + private RateLimiter getAmendOrderRateLimiter(BybitCategory category) { + switch (category) { + case LINEAR: + case INVERSE: + return rateLimiter(ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER); + case SPOT: + return rateLimiter(ORDER_AMEND_SPOT_RATE_LIMITER); + case OPTION: + return rateLimiter(ORDER_AMEND_OPTION_LIMITER); } - return cancelOrder; + return null; } } diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitAdaptersTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitAdaptersTest.java index 61dd2d4d9c0..a8037547898 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitAdaptersTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/BybitAdaptersTest.java @@ -19,10 +19,6 @@ public void testGuessSymbol() { assertThat(guessSymbol("BTCDAI")).isEqualTo(new CurrencyPair("BTC", "DAI")); assertThat(guessSymbol("ABCDEFG")).isEqualTo(new CurrencyPair("ABCD", "EFG")); - assertThat(guessSymbol("BTCUSDT", BybitCategory.LINEAR)) - .isEqualTo(new FuturesContract("BTC/USDT/PERP")); - assertThat(guessSymbol("ETHPERP", BybitCategory.LINEAR)) - .isEqualTo(new FuturesContract("ETH/USDC/PERP")); } @Test 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 1906c779e54..5ca5120c96e 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,13 +1,31 @@ package org.knowm.xchange.bybit; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +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 static org.assertj.core.api.Assertions.catchThrowable; +import static org.knowm.xchange.bybit.BybitResilience.POSITION_SET_LEVERAGE_INVERSE_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER; import com.github.tomakehurst.wiremock.matching.ContainsPattern; +import io.github.resilience4j.ratelimiter.RateLimiter; +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import io.github.resilience4j.ratelimiter.RequestNotPermitted; +import jakarta.ws.rs.core.Response.Status; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +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; +import org.knowm.xchange.bybit.service.BybitAccountService; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.marketdata.Ticker; public class BybitExchangeTest extends BaseWiremockTest { @@ -43,4 +61,30 @@ public void testSymbolLoading() throws IOException { assertThat(bybitExchange.getExchangeMetaData().getInstruments()).hasSize(4); } + + @Test + public void rateLimiterTest() throws IOException { + Exchange bybitExchange = createExchange(); + bybitExchange + .getResilienceRegistries() + .rateLimiters() + .replace( + POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER, + RateLimiter.of( + POSITION_SET_LEVERAGE_LINEAR_RATE_LIMITER, + RateLimiterConfig.custom() + .limitRefreshPeriod(Duration.ofSeconds(1)) + .limitForPeriod(1) + .timeoutDuration(Duration.ofMillis(1)) + .build())); + initPostStub("/v5/position/set-leverage","/setLeverage.json5"); + BybitAccountService bybitAccountService = (BybitAccountService) bybitExchange.getAccountService(); + boolean bybitSetLeverageBybitResult; + Throwable exception = null; + for (int i = 0; i <= 2; i++) { + exception = catchThrowable(() -> bybitAccountService.setLeverage(new FuturesContract("ETH/USDT/PERP"), 1d)); + } + assertThat(exception).isInstanceOf(RequestNotPermitted.class); + + } } diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/TradeExample.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/TradeExample.java deleted file mode 100644 index b21a6a340fc..00000000000 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/TradeExample.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.knowm.xchange.bybit; - -import static org.knowm.xchange.Exchange.USE_SANDBOX; -import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; - -import java.io.IOException; -import java.math.BigDecimal; -import org.knowm.xchange.Exchange; -import org.knowm.xchange.ExchangeFactory; -import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; -import org.knowm.xchange.bybit.service.BybitTradeService; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.derivative.FuturesContract; -import org.knowm.xchange.dto.Order.OrderStatus; -import org.knowm.xchange.dto.Order.OrderType; -import org.knowm.xchange.dto.marketdata.Ticker; -import org.knowm.xchange.dto.trade.LimitOrder; -import org.knowm.xchange.dto.trade.MarketOrder; -import org.knowm.xchange.instrument.Instrument; - -public class TradeExample { - - public static void main(String[] args) { - try { - testTrade(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - public static void testTrade() throws IOException { - ExchangeSpecification exchangeSpecification = - new BybitExchange().getDefaultExchangeSpecification(); - exchangeSpecification.setApiKey(System.getProperty("test_api_key")); - exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); - exchangeSpecification.setExchangeSpecificParametersItem( - SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); - exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); - Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - Instrument ETH_USDT = new CurrencyPair("ETH/USDT"); - Instrument BTC_USDT_PERP = new FuturesContract(new CurrencyPair("BTC/USDT"), "PERP"); - Instrument ETH_USDT_PERP = new FuturesContract(new CurrencyPair("ETH/USDT"), "PERP"); - - // System.out.printf("Tickers SPOT %s", - // exchange.getMarketDataService().getTickers(BybitCategory.SPOT)); - // System.out.printf("Tickers LINEAR %s", - // exchange.getMarketDataService().getTickers(BybitCategory.LINEAR)); - // System.out.printf("Tickers INVERSE %s", - // exchange.getMarketDataService().getTickers(BybitCategory.INVERSE)); - // System.out.printf("Tickers OPTION %s", - // exchange.getMarketDataService().getTickers(BybitCategory.OPTION)); - System.out.printf( - "Wallets: %n%s%n", exchange.getAccountService().getAccountInfo().getWallets()); - Ticker tickerETH_USDT_PERP = exchange.getMarketDataService().getTicker(ETH_USDT_PERP); - Ticker tickerETH_USDT = exchange.getMarketDataService().getTicker(ETH_USDT); - System.out.println(tickerETH_USDT_PERP.toString()); - - System.out.printf( - "Instrument %s:%n %s", - ETH_USDT, exchange.getExchangeMetaData().getInstruments().get(ETH_USDT)); - System.out.printf( - "Instrument %s:%n %s", - ETH_USDT_PERP, exchange.getExchangeMetaData().getInstruments().get(ETH_USDT_PERP)); - BigDecimal minAmountSpot = - exchange.getExchangeMetaData().getInstruments().get(ETH_USDT).getMinimumAmount(); - - // sell - MarketOrder marketOrderSpot = new MarketOrder(OrderType.ASK, minAmountSpot, ETH_USDT); - String marketSpotOrderId = exchange.getTradeService().placeMarketOrder(marketOrderSpot); - System.out.println("Market Spot order id: " + marketSpotOrderId); - - BigDecimal minAmountFuture = - exchange.getExchangeMetaData().getInstruments().get(ETH_USDT_PERP).getMinimumAmount(); - - // long - String marketFutureOrderId = - exchange - .getTradeService() - .placeMarketOrder(new MarketOrder(OrderType.BID, minAmountFuture, ETH_USDT_PERP)); - System.out.println("Market Future order id: " + marketFutureOrderId); - - // short - LimitOrder limitOrderFuture = - new LimitOrder( - OrderType.ASK, - minAmountFuture, - ETH_USDT_PERP, - null, - null, - tickerETH_USDT_PERP.getHigh()); - String limitFutureOrderId = exchange.getTradeService().placeLimitOrder(limitOrderFuture); - System.out.println("Limit Future order id: " + limitFutureOrderId); - - // amend order by order id - LimitOrder amendOrder1 = - new LimitOrder( - limitOrderFuture.getType(), - limitOrderFuture.getOriginalAmount().multiply(new BigDecimal(2)), - limitOrderFuture.getInstrument(), - limitFutureOrderId, - limitOrderFuture.getTimestamp(), - tickerETH_USDT_PERP.getHigh(), - null, - null, - null, - OrderStatus.PENDING_NEW, - null); - String limitFutureOrderAmend1 = - ((BybitTradeService) exchange.getTradeService()).amendOrder(amendOrder1); - System.out.printf("amend limit order %s%n", limitFutureOrderAmend1); - - // amend order by user id - LimitOrder amendOrder2 = - new LimitOrder( - limitOrderFuture.getType(), - limitOrderFuture.getOriginalAmount(), - limitOrderFuture.getInstrument(), - "", - limitOrderFuture.getTimestamp(), - tickerETH_USDT_PERP.getLast(), - null, - null, - null, - OrderStatus.PENDING_NEW, - limitOrderFuture.getUserReference()); - String limitFutureOrderAmend2 = - ((BybitTradeService) exchange.getTradeService()).amendOrder(amendOrder2); - System.out.printf("amend limit order %s%n", limitFutureOrderAmend2); - } -} diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelAllOrdersExample.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelAllOrdersExample.java new file mode 100644 index 00000000000..b0cf7dd1f09 --- /dev/null +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelAllOrdersExample.java @@ -0,0 +1,78 @@ +package org.knowm.xchange.bybit.examples; + +import static org.knowm.xchange.Exchange.USE_SANDBOX; +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collection; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; +import org.knowm.xchange.bybit.service.BybitAccountService; +import org.knowm.xchange.bybit.service.BybitTradeService.BybitCancelAllOrdersParams; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.service.trade.TradeService; + +public class BybitCancelAllOrdersExample { + + public static void main(String[] args) { + try { + testCancelAllOrders(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void testCancelAllOrders() throws IOException { + ExchangeSpecification exchangeSpecification = + new BybitExchange().getDefaultExchangeSpecification(); + exchangeSpecification.setApiKey(System.getProperty("test_api_key")); + exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); + exchangeSpecification.setExchangeSpecificParametersItem( + SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); + exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); + Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + Instrument ETH_USDT = new CurrencyPair("ETH/USDT"); + Instrument ETH_USDT_PERP = new FuturesContract(new CurrencyPair("ETH/USDT"), "PERP"); + Ticker tickerETH_USDT_PERP = exchange.getMarketDataService().getTicker(ETH_USDT_PERP); + BybitAccountService bybitAccountService = (BybitAccountService) exchange.getAccountService(); + + //switch mode to one-way + bybitAccountService.switchPositionMode(BybitCategory.LINEAR, ETH_USDT_PERP, "USDT", 0); + System.out.printf( + "Instrument %s:%n %s", + ETH_USDT_PERP, exchange.getExchangeMetaData().getInstruments().get(ETH_USDT_PERP)); + BigDecimal price = tickerETH_USDT_PERP.getLow().add(new BigDecimal("50").negate()); + LimitOrder limitFutureOrder = + new LimitOrder( + OrderType.BID, + new BigDecimal("0.05"), + ETH_USDT_PERP, + null, + null, price); + LimitOrder limitSpotOrder = + new LimitOrder( + OrderType.BID, + new BigDecimal("0.05"), + ETH_USDT, + null, + null, price); + String limitFutureOrderId = exchange.getTradeService().placeLimitOrder(limitFutureOrder); + String limitSpotOrderId = exchange.getTradeService().placeLimitOrder(limitSpotOrder); + TradeService tradeService = exchange.getTradeService(); + Collection resultSpot = tradeService.cancelAllOrders(new BybitCancelAllOrdersParams(BybitCategory.SPOT, null)); + Collection resultFuture0 = tradeService.cancelAllOrders(new BybitCancelAllOrdersParams(BybitCategory.LINEAR, ETH_USDT_PERP)); + System.out.printf("cancel all orders SPOT, result %s%n", resultSpot); + System.out.printf("cancel all orders LINEAR, result %s%n", resultFuture0); + + } +} diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelOrderExample.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelOrderExample.java new file mode 100644 index 00000000000..139f11e8c82 --- /dev/null +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitCancelOrderExample.java @@ -0,0 +1,90 @@ +package org.knowm.xchange.bybit.examples; + +import static org.knowm.xchange.Exchange.USE_SANDBOX; +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Random; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; +import org.knowm.xchange.bybit.service.BybitAccountService; +import org.knowm.xchange.bybit.service.BybitTradeService.BybitCancelOrderParams; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderStatus; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.instrument.Instrument; + +public class BybitCancelOrderExample { + public static void main(String[] args) { + try { + testCancelOrders(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void testCancelOrders() throws IOException { + ExchangeSpecification exchangeSpecification = + new BybitExchange().getDefaultExchangeSpecification(); + exchangeSpecification.setApiKey(System.getProperty("test_api_key")); + exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); + exchangeSpecification.setExchangeSpecificParametersItem( + SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); + exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); + Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + Instrument ETH_USDT = new CurrencyPair("ETH/USDT"); + Instrument ETH_USDT_PERP = new FuturesContract(new CurrencyPair("ETH/USDT"), "PERP"); + Ticker tickerETH_USDT_PERP = exchange.getMarketDataService().getTicker(ETH_USDT_PERP); + BybitAccountService bybitAccountService = (BybitAccountService) exchange.getAccountService(); + + //switch mode to one-way + bybitAccountService.switchPositionMode(BybitCategory.LINEAR, ETH_USDT_PERP, "USDT", 0); + System.out.printf( + "Instrument %s:%n %s %n", + ETH_USDT_PERP, exchange.getExchangeMetaData().getInstruments().get(ETH_USDT_PERP)); + BigDecimal price = tickerETH_USDT_PERP.getLow().add(new BigDecimal("50").negate()); + LimitOrder limitSpotOrder = + new LimitOrder( + OrderType.BID, + new BigDecimal("0.05"), + ETH_USDT, + null, + null, price); + String userReference0 = String.valueOf(new Random().nextLong()); + LimitOrder limitFutureOrder0 = + new LimitOrder( + OrderType.BID, + new BigDecimal("0.05"), + ETH_USDT_PERP, + null, + null, price, null, null, null, OrderStatus.PENDING_NEW, userReference0); + String userReference1 = String.valueOf(new Random().nextLong()); + LimitOrder limitFutureOrder1 = + new LimitOrder( + OrderType.BID, + new BigDecimal("0.05"), + ETH_USDT_PERP, + null, + null, price, null, null, null, OrderStatus.PENDING_NEW, userReference1); + + String limitSpotOrderId = exchange.getTradeService().placeLimitOrder(limitSpotOrder); + String limitFutureOrderId0 = exchange.getTradeService().placeLimitOrder(limitFutureOrder0); + String limitFutureOrderId1 = exchange.getTradeService().placeLimitOrder(limitFutureOrder1); + + boolean resultSpot = exchange.getTradeService().cancelOrder(new BybitCancelOrderParams(ETH_USDT, limitSpotOrderId, "")); + boolean resultFuture0 = exchange.getTradeService().cancelOrder(new BybitCancelOrderParams(ETH_USDT_PERP, limitFutureOrderId0, userReference0)); + boolean resultFuture1 = exchange.getTradeService().cancelOrder(new BybitCancelOrderParams(ETH_USDT_PERP, "", userReference1)); + System.out.printf("cancel order SPOT, result %s%n", resultSpot); + System.out.printf("cancel order LINEAR0, result %s%n", resultFuture0); + System.out.printf("cancel order LINEAR1, result %s%n", resultFuture1); + + } +} diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitPlaceOrderExample.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitPlaceOrderExample.java new file mode 100644 index 00000000000..03cac8bff0d --- /dev/null +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitPlaceOrderExample.java @@ -0,0 +1,85 @@ +package org.knowm.xchange.bybit.examples; + +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; + +import org.knowm.xchange.bybit.dto.trade.details.BybitHedgeMode; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; +import org.knowm.xchange.bybit.service.BybitAccountService; + +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.instrument.Instrument; + +public class BybitPlaceOrderExample { + + public static void main(String[] args) throws InterruptedException { + try { + testOrder(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void testOrder() throws IOException { + ExchangeSpecification exchangeSpecification = + new BybitExchange().getDefaultExchangeSpecification(); + exchangeSpecification.setApiKey(System.getProperty("test_api_key")); + exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); + exchangeSpecification.setExchangeSpecificParametersItem( + SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); + exchangeSpecification.setExchangeSpecificParametersItem(Exchange.USE_SANDBOX, true); + Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + + Instrument ETH_USDT_PERP = new FuturesContract(new CurrencyPair("ETH/USDT"), "PERP"); + + System.out.printf("Wallets: %n%s%n", + exchange.getAccountService().getAccountInfo().getWallets()); + Ticker ticker = exchange.getMarketDataService().getTicker(ETH_USDT_PERP); + + System.out.printf( + "Instrument %s:%n %s %n", + ETH_USDT_PERP, exchange.getExchangeMetaData().getInstruments().get(ETH_USDT_PERP)); + + BigDecimal minAmountFuture = + exchange.getExchangeMetaData().getInstruments().get(ETH_USDT_PERP).getMinimumAmount(); + BybitAccountService bybitAccountService = (BybitAccountService) exchange.getAccountService(); + //switch mode to two-way + bybitAccountService.switchPositionMode(BybitCategory.LINEAR, ETH_USDT_PERP, "USDT", 3); + MarketOrder marketOrder = + new MarketOrder( + OrderType.BID, + minAmountFuture, + ETH_USDT_PERP); + marketOrder.addOrderFlag(BybitHedgeMode.TWOWAY); + String marketOrderId = exchange.getTradeService().placeMarketOrder(marketOrder); + System.out.println("market order id: " + marketOrderId); + + LimitOrder limitOrder = + new LimitOrder( + OrderType.EXIT_BID, + minAmountFuture, + BigDecimal.ZERO, + ETH_USDT_PERP, + "", + new Date(), + ticker.getLow()); + limitOrder.addOrderFlag(BybitTimeInForce.POSTONLY); + limitOrder.addOrderFlag(BybitHedgeMode.TWOWAY); + String limitOrderId = exchange.getTradeService().placeLimitOrder(limitOrder); + System.out.println("limit order id: " + limitOrderId); + } +} diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitRateLimiterTestExample.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitRateLimiterTestExample.java new file mode 100644 index 00000000000..c18fb6c0c21 --- /dev/null +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/examples/BybitRateLimiterTestExample.java @@ -0,0 +1,100 @@ +package org.knowm.xchange.bybit.examples; + +import static org.knowm.xchange.Exchange.USE_SANDBOX; +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; +import static org.knowm.xchange.bybit.BybitResilience.GLOBAL_RATE_LIMITER; +import static org.knowm.xchange.bybit.BybitResilience.ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER; + +import java.io.IOException; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import org.knowm.xchange.Exchange; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; +import org.knowm.xchange.bybit.service.BybitTradeService; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.instrument.Instrument; + +public class BybitRateLimiterTestExample { + + public static void main(String[] args) { + try { + testRateLimits(); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + static Instrument ETH_USDT_PERP = new FuturesContract(new CurrencyPair("ETH/USDT"), "PERP"); + + private static void testRateLimits() throws IOException, InterruptedException { + SimpleDateFormat sdf = new SimpleDateFormat("mm-ss-SSS"); + ExchangeSpecification exchangeSpecification = + new BybitExchange().getDefaultExchangeSpecification(); + exchangeSpecification.setApiKey(System.getProperty("test_api_key")); + exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); + exchangeSpecification.setExchangeSpecificParametersItem( + SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); + exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); + exchangeSpecification.getResilience().setRetryEnabled(true); + Exchange exchange = ExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + + Ticker tickerETH_USDT_PERP = exchange.getMarketDataService().getTicker(ETH_USDT_PERP); + LimitOrder limitOrderFuture = createOrder("", tickerETH_USDT_PERP.getHigh()); + String limitFutureOrderId = exchange.getTradeService().placeLimitOrder(limitOrderFuture); + BybitTradeService bybitTradeService = (BybitTradeService) exchange.getTradeService(); + exchange.getResilienceRegistries().rateLimiters().rateLimiter(GLOBAL_RATE_LIMITER) + .getEventPublisher() + .onSuccess(event -> System.out.println(sdf.format(new Date()) +" event type " + event.getEventType() + + " availablePermissions: " + exchange.getResilienceRegistries() + .rateLimiters() + .rateLimiter(ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER).getMetrics() + .getAvailablePermissions())) + .onFailure(event -> System.out.println(sdf.format(new Date()) +" event type " + event.getEventType()+ + " availablePermissions: " + exchange.getResilienceRegistries() + .rateLimiters() + .rateLimiter(ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER).getMetrics() + .getAvailablePermissions())); + System.out.println(sdf.format(new Date()) +" RateLimiterEnabled: "+exchange.getExchangeSpecification().getResilience().isRateLimiterEnabled()); + ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3); + for (int i = 0; i < 8; i++) { + int finalI = i; +// System.out.println(sdf.format(new Date()) + +// " Amend order, availablePermissions: " + exchange.getResilienceRegistries() +// .rateLimiters() +// .rateLimiter(ORDER_AMEND_LINEAR_AND_INVERSE_RATE_LIMITER).getMetrics() +// .getAvailablePermissions()); + Thread.sleep(1); + executor.execute(() -> { + try { + String result = bybitTradeService.changeOrder(createOrder(limitFutureOrderId, + tickerETH_USDT_PERP.getHigh().add(new BigDecimal("0.1" + finalI)))); + System.out.println(sdf.format(new Date()) +" amend order # "+result); + } catch (IOException e) { + System.out.println(sdf.format(new Date()) +" IOException "+e.getMessage()); + } + }); + } + System.out.println(sdf.format(new Date()) +" end"); + } + + private static LimitOrder createOrder(String orderId, BigDecimal price) throws IOException { + return new LimitOrder( + OrderType.ASK, + new BigDecimal("0.05"), + ETH_USDT_PERP, + orderId, + null, + price); + } + +} diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BaseWiremockTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BaseWiremockTest.java index 0d7bae22af5..f8077dd2a1e 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BaseWiremockTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BaseWiremockTest.java @@ -2,6 +2,7 @@ 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.post; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; @@ -13,7 +14,6 @@ import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Rule; -import org.knowm.xchange.Exchange; import org.knowm.xchange.ExchangeFactory; import org.knowm.xchange.ExchangeSpecification; import org.knowm.xchange.bybit.BybitExchange; @@ -22,8 +22,8 @@ public class BaseWiremockTest { @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); - public Exchange createExchange() throws IOException { - Exchange exchange = + public BybitExchange createExchange() throws IOException { + BybitExchange exchange = ExchangeFactory.INSTANCE.createExchangeWithoutSpecification(BybitExchange.class); ExchangeSpecification specification = exchange.getDefaultExchangeSpecification(); specification.setHost("localhost"); @@ -46,6 +46,14 @@ protected void initGetStub(String url, String responseBody) throws IOException { .withBody(IOUtils.resourceToString(responseBody, StandardCharsets.UTF_8)))); } + /** + * + * @param baseUrl baseUrl + * @param responseBody responseBody + * @param queryParams queryParams + * @param stringValuePattern stringValuePattern + * @throws IOException IOException + */ protected void initGetStub( String baseUrl, String responseBody, @@ -61,4 +69,14 @@ protected void initGetStub( .withHeader("Content-Type", "application/json") .withBody(IOUtils.resourceToString(responseBody, StandardCharsets.UTF_8)))); } + + protected void initPostStub(String url, String responseBody) throws IOException { + stubFor( + post(urlPathEqualTo(url)) + .willReturn( + aResponse() + .withStatus(Status.OK.getStatusCode()) + .withHeader("Content-Type", "application/json") + .withBody(IOUtils.resourceToString(responseBody, StandardCharsets.UTF_8)))); + } } diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceRawTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceRawTest.java index f268002e9dc..335716ff720 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceRawTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceRawTest.java @@ -7,16 +7,16 @@ import org.junit.Before; import org.junit.Test; import org.knowm.xchange.Exchange; -import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.Bybit; +import org.knowm.xchange.bybit.BybitExchange; import org.knowm.xchange.bybit.dto.BybitResult; import org.knowm.xchange.bybit.dto.account.allcoins.BybitAllCoinBalance; import org.knowm.xchange.bybit.dto.account.allcoins.BybitAllCoinsBalance; -import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRate; -import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRates; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountBalance; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitCoinWalletBalance; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitWalletBalance; +import org.knowm.xchange.client.ResilienceRegistries; public class BybitAccountServiceRawTest extends BaseWiremockTest { @@ -24,8 +24,8 @@ public class BybitAccountServiceRawTest extends BaseWiremockTest { @Before public void setUp() throws Exception { - Exchange bybitExchange = createExchange(); - bybitAccountServiceRaw = new BybitAccountServiceRaw(bybitExchange); + BybitExchange bybitExchange = createExchange(); + bybitAccountServiceRaw = new BybitAccountServiceRaw(bybitExchange, new ResilienceRegistries()); } @Test @@ -95,21 +95,4 @@ public void testGetAllCoinsBalances() throws IOException { assertThat(coinBalance.getWalletBalance()).isEqualTo("0"); assertThat(coinBalance.getBonus()).isNull(); } - - @Test - public void testGetFeeRates() throws IOException { - initGetStub("/v5/account/fee-rate", "/getFeeRates.json5"); - - BybitResult bybitFeeRatesBybitResult = - bybitAccountServiceRaw.getFeeRates(BybitCategory.SPOT, "ETHUSDT"); - - BybitFeeRates feeRates = bybitFeeRatesBybitResult.getResult(); - - assertThat(feeRates.getList()).hasSize(1); - BybitFeeRate feeRate = feeRates.getList().get(0); - - assertThat(feeRate.getSymbol()).isEqualTo("ETHUSDT"); - assertThat(feeRate.getTakerFeeRate()).isEqualTo("0.0006"); - assertThat(feeRate.getMakerFeeRate()).isEqualTo("0.0001"); - } } diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceTest.java index 932ba2a7e5f..95f85b4c725 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitAccountServiceTest.java @@ -1,25 +1,48 @@ package org.knowm.xchange.bybit.service; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +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 static org.junit.Assert.fail; +import jakarta.ws.rs.core.Response.Status; import java.io.IOException; import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.IOUtils; +import org.junit.Before; import org.junit.Test; import org.knowm.xchange.Exchange; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.BybitResult; +import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRate; +import org.knowm.xchange.bybit.dto.account.feerates.BybitFeeRates; import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; +import org.knowm.xchange.bybit.service.BybitTradeService.BybitCancelAllOrdersParams; import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; import org.knowm.xchange.dto.account.AccountInfo; +import org.knowm.xchange.instrument.Instrument; public class BybitAccountServiceTest extends BaseWiremockTest { + static BybitExchange bybitExchange; + static BybitAccountService bybitAccountService; + + public void setUp() throws IOException { + bybitExchange = createExchange(); + bybitAccountService = + new BybitAccountService(bybitExchange, BybitAccountType.UNIFIED,bybitExchange.getResilienceRegistries()); + } + @Test public void testGetWalletBalancesWithUnified() throws IOException { - Exchange bybitExchange = createExchange(); - BybitAccountService bybitAccountService = - new BybitAccountService(bybitExchange, BybitAccountType.UNIFIED); - + setUp(); initGetStub("/v5/account/wallet-balance", "/getWalletBalance.json5"); - AccountInfo accountInfo = bybitAccountService.getAccountInfo(); assertThat(accountInfo.getWallet().getBalance(new Currency("BTC")).getTotal()) .isEqualTo(new BigDecimal("0")); @@ -29,16 +52,59 @@ public void testGetWalletBalancesWithUnified() throws IOException { @Test public void testGetAllCoinsBalanceWithFund() throws IOException { - Exchange bybitExchange = createExchange(); + BybitExchange bybitExchange = createExchange(); BybitAccountService bybitAccountService = - new BybitAccountService(bybitExchange, BybitAccountType.FUND); + new BybitAccountService(bybitExchange, BybitAccountType.FUND,bybitExchange.getResilienceRegistries()); initGetStub("/v5/asset/transfer/query-account-coins-balance", "/getAllCoinsBalance.json5"); - AccountInfo accountInfo = bybitAccountService.getAccountInfo(); assertThat(accountInfo.getWallet().getBalance(new Currency("USDC")).getTotal()) .isEqualTo(new BigDecimal("0")); assertThat(accountInfo.getWallet().getBalance(new Currency("USDC")).getAvailable()) .isEqualTo(new BigDecimal("0")); } + + @Test + public void testGetFeeRate() throws IOException { + setUp(); + initGetStub("/v5/account/fee-rate", "/getFeeRates.json5"); + Instrument ETH_USDT = new CurrencyPair("ETH/USDT"); + BybitResult bybitFeeRatesBybitResult = + bybitAccountService.getFeeRates(BybitCategory.SPOT, ETH_USDT); + BybitFeeRates feeRates = bybitFeeRatesBybitResult.getResult(); + assertThat(feeRates.getList()).hasSize(1); + BybitFeeRate feeRate = feeRates.getList().get(0); + + assertThat(feeRate.getSymbol()).isEqualTo("ETHUSDT"); + assertThat(feeRate.getTakerFeeRate()).isEqualTo("0.0006"); + assertThat(feeRate.getMakerFeeRate()).isEqualTo("0.0001"); + } + + @Test + public void testSetLeverage() throws IOException { + setUp(); + initPostStub("/v5/position/set-leverage", "/setLeverage.json5"); + try { + bybitAccountService.setLeverage( new CurrencyPair("ETH/USDT"),1d); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException ignored) { + + } + boolean bybitSetLeverageBybitResult = + bybitAccountService.setLeverage( new FuturesContract("ETH/USDT/PERP"),1d); + assertThat(bybitSetLeverageBybitResult).isTrue(); + } + + @Test + public void testSwitchPositionMode() throws IOException { + setUp(); + initPostStub("/v5/position/switch-mode", "/switchPositionMode.json5"); + + BybitAccountService bybitAccountService = (BybitAccountService) bybitExchange.getAccountService(); + boolean bybitSwitchPositionModeResult = bybitAccountService.switchPositionMode( BybitCategory.LINEAR,new FuturesContract("BTC/USDT/PERP"),null,0); + + assertThat(bybitSwitchPositionModeResult).isTrue(); + } + + } diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceRawTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceRawTest.java index e4ffed1bfb9..34450da652a 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceRawTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceRawTest.java @@ -5,6 +5,8 @@ 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 static org.knowm.xchange.bybit.BybitAdapters.convertToBybitSymbol; +import static org.knowm.xchange.bybit.dto.trade.BybitSide.BUY; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -15,22 +17,29 @@ import org.apache.commons.io.IOUtils; import org.junit.Test; import org.knowm.xchange.Exchange; +import org.knowm.xchange.bybit.BybitAdapters; +import org.knowm.xchange.bybit.BybitExchange; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.BybitResult; import org.knowm.xchange.bybit.dto.trade.BybitOrderResponse; import org.knowm.xchange.bybit.dto.trade.BybitOrderStatus; +import org.knowm.xchange.bybit.dto.trade.BybitOrderType; import org.knowm.xchange.bybit.dto.trade.BybitSide; import org.knowm.xchange.bybit.dto.trade.details.BybitOrderDetail; import org.knowm.xchange.bybit.dto.trade.details.BybitOrderDetails; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; import org.knowm.xchange.bybit.dto.trade.details.linear.BybitLinearOrderDetail; import org.knowm.xchange.bybit.dto.trade.details.spot.BybitSpotOrderDetail; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.instrument.Instrument; public class BybitTradeServiceRawTest extends BaseWiremockTest { @Test public void testGetBybitLinearDetailOrder() throws IOException { - Exchange bybitExchange = createExchange(); - BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange); + BybitExchange bybitExchange = createExchange(); + BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange, bybitExchange.getResilienceRegistries()); String responseFilePath = "/getOrderDetailsLinear.json5"; initGetStub("/v5/order/realtime", responseFilePath); @@ -132,8 +141,8 @@ public void testGetBybitLinearDetailOrder() throws IOException { @Test public void testGetBybitSpotDetailOrder() throws IOException { - Exchange bybitExchange = createExchange(); - BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange); + BybitExchange bybitExchange = createExchange(); + BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange, bybitExchange.getResilienceRegistries()); String responseFilePath = "/getOrderDetailsSpot.json5"; initGetStub("/v5/order/realtime", responseFilePath); @@ -224,8 +233,8 @@ public void testGetBybitSpotDetailOrder() throws IOException { @Test public void testPlaceBybitMarketOrder() throws IOException { - Exchange bybitExchange = createExchange(); - BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange); + BybitExchange bybitExchange = createExchange(); + BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange, bybitExchange.getResilienceRegistries()); String orderPlacementResponse = "{\n" @@ -248,8 +257,21 @@ public void testPlaceBybitMarketOrder() throws IOException { .withBody(orderPlacementResponse))); BybitResult order = - bybitAccountServiceRaw.placeMarketOrder( - BybitCategory.SPOT, "BTCUSDT", BybitSide.BUY, BigDecimal.valueOf(0.1), null); + bybitAccountServiceRaw.placeOrder( + BybitCategory.SPOT, + convertToBybitSymbol(new CurrencyPair("BTC/USDT")), + BybitAdapters.getSideString(OrderType.BID), + BybitOrderType.MARKET, + BigDecimal.valueOf(0.1), + null, + "", + null, + null, + null, + null, + false, + 0, + BybitTimeInForce.IOC); ObjectMapper mapper = new ObjectMapper(); JsonNode responseObject = mapper.readTree(orderPlacementResponse); @@ -267,8 +289,8 @@ public void testPlaceBybitMarketOrder() throws IOException { @Test public void testPlaceBybitLimitOrder() throws IOException { - Exchange bybitExchange = createExchange(); - BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange); + BybitExchange bybitExchange = createExchange(); + BybitTradeServiceRaw bybitAccountServiceRaw = new BybitTradeServiceRaw(bybitExchange, bybitExchange.getResilienceRegistries()); String orderPlacementResponse = "{\n" @@ -291,13 +313,21 @@ public void testPlaceBybitLimitOrder() throws IOException { .withBody(orderPlacementResponse))); BybitResult order = - bybitAccountServiceRaw.placeLimitOrder( + bybitAccountServiceRaw.placeOrder( BybitCategory.SPOT, - "BTCUSDT", - BybitSide.BUY, + convertToBybitSymbol(new CurrencyPair("BTC/USDT")), + BybitAdapters.getSideString(OrderType.BID), + BybitOrderType.LIMIT, BigDecimal.valueOf(0.1), BigDecimal.valueOf(1000), - null); + "", + null, + null, + null, + null, + false, + 0, + BybitTimeInForce.GTC); ObjectMapper mapper = new ObjectMapper(); JsonNode responseObject = mapper.readTree(orderPlacementResponse); diff --git a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceTest.java b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceTest.java index 1f1bb0908a0..f4f90d2336d 100644 --- a/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceTest.java +++ b/xchange-bybit/src/test/java/org/knowm/xchange/bybit/service/BybitTradeServiceTest.java @@ -1,99 +1,48 @@ 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.post; -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 static org.junit.Assert.fail; +import static org.knowm.xchange.currency.CurrencyPair.BTC_USDT; + import com.github.tomakehurst.wiremock.matching.ContainsPattern; -import jakarta.ws.rs.core.Response.Status; import java.io.IOException; import java.math.BigDecimal; import java.util.Collection; +import java.util.Date; +import org.junit.Before; import org.junit.Test; -import org.knowm.xchange.Exchange; +import org.knowm.xchange.bybit.BybitExchange; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.service.BybitTradeService.BybitCancelAllOrdersParams; +import org.knowm.xchange.bybit.service.BybitTradeService.BybitCancelOrderParams; import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderStatus; import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.trade.LimitOrder; import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.instrument.Instrument; public class BybitTradeServiceTest extends BaseWiremockTest { + BybitExchange bybitExchange; + BybitTradeService bybitTradeService; + + @Before + public void setUp() throws IOException { + bybitExchange = createExchange(); + bybitTradeService = new BybitTradeService(bybitExchange, + bybitExchange.getResilienceRegistries()); + } + @Test public void testGetBybitOrder() throws IOException { - Exchange bybitExchange = createExchange(); - BybitTradeService bybitAccountService = new BybitTradeService(bybitExchange); - - String orderDetails = - "{\n" - + " \"retCode\": 0,\n" - + " \"retMsg\": \"OK\",\n" - + " \"result\": {\n" - + " \"list\": [\n" - + " {\n" - + " \"orderId\": \"fd4300ae-7847-404e-b947-b46980a4d140\",\n" - + " \"orderLinkId\": \"test-000005\",\n" - + " \"blockTradeId\": \"\",\n" - + " \"symbol\": \"ETHUSDT\",\n" - + " \"price\": \"1600.00\",\n" - + " \"qty\": \"0.10\",\n" - + " \"side\": \"Buy\",\n" - + " \"isLeverage\": \"\",\n" - + " \"positionIdx\": 1,\n" - + " \"orderStatus\": \"New\",\n" - + " \"cancelType\": \"UNKNOWN\",\n" - + " \"rejectReason\": \"EC_NoError\",\n" - + " \"avgPrice\": \"0\",\n" - + " \"leavesQty\": \"0.10\",\n" - + " \"leavesValue\": \"160\",\n" - + " \"cumExecQty\": \"0.00\",\n" - + " \"cumExecValue\": \"0\",\n" - + " \"cumExecFee\": \"0\",\n" - + " \"timeInForce\": \"GTC\",\n" - + " \"orderType\": \"Limit\",\n" - + " \"stopOrderType\": \"UNKNOWN\",\n" - + " \"orderIv\": \"\",\n" - + " \"triggerPrice\": \"0.00\",\n" - + " \"takeProfit\": \"2500.00\",\n" - + " \"stopLoss\": \"1500.00\",\n" - + " \"tpTriggerBy\": \"LastPrice\",\n" - + " \"slTriggerBy\": \"LastPrice\",\n" - + " \"triggerDirection\": 0,\n" - + " \"triggerBy\": \"UNKNOWN\",\n" - + " \"lastPriceOnCreated\": \"\",\n" - + " \"reduceOnly\": false,\n" - + " \"closeOnTrigger\": false,\n" - + " \"smpType\": \"None\",\n" - + " \"smpGroup\": 0,\n" - + " \"smpOrderId\": \"\",\n" - + " \"tpslMode\": \"Full\",\n" - + " \"tpLimitPrice\": \"\",\n" - + " \"slLimitPrice\": \"\",\n" - + " \"placeType\": \"\",\n" - + " \"createdTime\": \"1684738540559\",\n" - + " \"updatedTime\": \"1684738540561\"\n" - + " }\n" - + " ],\n" - + " \"nextPageCursor\": \"page_args%3Dfd4300ae-7847-404e-b947-b46980a4d140%26symbol%3D6%26\",\n" - + " \"category\": \"linear\"\n" - + " },\n" - + " \"retExtInfo\": {},\n" - + " \"time\": 1684765770483\n" - + "}"; - - stubFor( - get(urlPathEqualTo("/v5/order/realtime")) - .withQueryParam("orderId", new ContainsPattern("fd4300ae-7847-404e-b947-b46980a4d140")) - .willReturn( - aResponse() - .withStatus(Status.OK.getStatusCode()) - .withHeader("Content-Type", "application/json") - .withBody(orderDetails))); - - Collection orders = bybitAccountService.getOrder("fd4300ae-7847-404e-b947-b46980a4d140"); + initGetStub("/v5/order/realtime", "/getOrder.json5", "orderId", + new ContainsPattern("fd4300ae-7847-404e-b947-b46980a4d140")); + + Collection orders = bybitTradeService.getOrder("fd4300ae-7847-404e-b947-b46980a4d140"); assertThat(orders.size()).isEqualTo(1); Order order = (Order) orders.toArray()[0]; @@ -106,33 +55,80 @@ public void testGetBybitOrder() throws IOException { @Test public void testPlaceBybitOrder() throws IOException { - Exchange bybitExchange = createExchange(); - BybitTradeService bybitAccountService = new BybitTradeService(bybitExchange); - - String orderPlacementResponse = - "{\n" - + " \"retCode\": 0,\n" - + " \"retMsg\": \"OK\",\n" - + " \"result\": {\n" - + " \"orderId\": \"1321003749386327552\",\n" - + " \"orderLinkId\": \"spot-test-postonly\"\n" - + " },\n" - + " \"retExtInfo\": {},\n" - + " \"time\": 1672211918471\n" - + "}"; - - stubFor( - post(urlPathEqualTo("/v5/order/create")) - .willReturn( - aResponse() - .withStatus(Status.OK.getStatusCode()) - .withHeader("Content-Type", "application/json") - .withBody(orderPlacementResponse))); + initPostStub("/v5/order/create", "/placeMarketOrder.json5"); + MarketOrder marketOrder = new MarketOrder(OrderType.ASK, new BigDecimal("0.1"), + new CurrencyPair("BTC", "USDT")); + String marketOrderId = bybitTradeService.placeMarketOrder(marketOrder); + assertThat(marketOrderId).isEqualTo("1321003749386327552"); + + LimitOrder limitOrder = new LimitOrder(OrderType.EXIT_BID, new BigDecimal("0.1"), + new CurrencyPair("BTC", "USDT"), "", new Date(1672211918471L), new BigDecimal("110")); + String limitOrderId = bybitTradeService.placeLimitOrder(limitOrder); + assertThat(limitOrderId).isEqualTo("1321003749386327552"); + } + + @Test + public void testChangeBybitOrder() throws IOException { + initPostStub("/v5/order/amend", "/changeOrder.json5"); + + LimitOrder limitOrder = new LimitOrder(OrderType.BID, new BigDecimal("0.1"), + new CurrencyPair("BTC", "USDT"), "", new Date(1672211918471L), new BigDecimal("110")); String orderId = - bybitAccountService.placeMarketOrder( - new MarketOrder(OrderType.ASK, new BigDecimal("0.1"), new CurrencyPair("BTC", "USDT"))); + bybitTradeService.changeOrder(limitOrder); + + assertThat(orderId).isEqualTo("c6f055d9-7f21-4079-913d-e6523a9cfffa"); + } + + @Test + public void testCancelBybitOrder() throws IOException { + initPostStub("/v5/order/cancel", "/cancelOrder.json5"); + + Instrument BTC_USDT_PERP = new FuturesContract("BTC/USDT/PERP"); + try { + bybitTradeService.cancelOrder(new BybitCancelOrderParams(BTC_USDT, "", "")); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException ignored) { + } + boolean resultSpot = bybitTradeService.cancelOrder( + new BybitCancelOrderParams(BTC_USDT, "c6f055d9-7f21-4079-913d-e6523a9cfffa", "")); + boolean resultFuture0 = bybitTradeService.cancelOrder( + new BybitCancelOrderParams(BTC_USDT_PERP, "c6f055d9-7f21-4079-913d-e6523a9cfffa", "")); + boolean resultFuture1 = bybitTradeService.cancelOrder( + new BybitCancelOrderParams(BTC_USDT_PERP, "", "linear-004")); + assertThat(resultSpot).isTrue(); + assertThat(resultFuture0).isTrue(); + assertThat(resultFuture1).isTrue(); + } + + @Test + public void testCancelAllBybitOrder() throws IOException { + initPostStub("/v5/order/cancel-all", "/cancelAllOrders.json5"); + Instrument BTC_USDT_PERP = new FuturesContract("BTC/USDT/PERP"); + + try { + bybitTradeService.cancelAllOrders(new BybitCancelAllOrdersParams( + null, null)); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException ignored) { + + } + try { + bybitTradeService.cancelAllOrders(new BybitCancelAllOrdersParams(BybitCategory.LINEAR, null)); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException ignored) { + + } + Collection resultSpot = bybitTradeService.cancelAllOrders( + new BybitCancelAllOrdersParams( + BybitCategory.SPOT, null)); + + Collection resultFuture0 = bybitTradeService.cancelAllOrders( + new BybitCancelAllOrdersParams(BybitCategory.LINEAR, BTC_USDT_PERP)); + + assertThat(resultSpot.stream().findFirst().get()).isEqualTo("1616024329462743808"); + assertThat(resultFuture0.stream().findFirst().get()).isEqualTo("1616024329462743808"); - assertThat(orderId).isEqualTo("1321003749386327552"); } + } diff --git a/xchange-bybit/src/test/resources/cancelAllOrders.json5 b/xchange-bybit/src/test/resources/cancelAllOrders.json5 new file mode 100644 index 00000000000..d175af06a13 --- /dev/null +++ b/xchange-bybit/src/test/resources/cancelAllOrders.json5 @@ -0,0 +1,19 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "list": [ + { + "orderId": "1616024329462743808", + "orderLinkId": "1616024329462743809" + }, + { + "orderId": "1616024287544869632", + "orderLinkId": "1616024287544869633" + } + ], + "success": "1" + }, + "retExtInfo": {}, + "time": 1707381118116 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/cancelOrder.json5 b/xchange-bybit/src/test/resources/cancelOrder.json5 new file mode 100644 index 00000000000..f7a505d3c12 --- /dev/null +++ b/xchange-bybit/src/test/resources/cancelOrder.json5 @@ -0,0 +1,10 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "orderId": "c6f055d9-7f21-4079-913d-e6523a9cfffa", + "orderLinkId": "linear-004" + }, + "retExtInfo": {}, + "time": 1672217377164 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/changeOrder.json5 b/xchange-bybit/src/test/resources/changeOrder.json5 new file mode 100644 index 00000000000..9b9c5884d1d --- /dev/null +++ b/xchange-bybit/src/test/resources/changeOrder.json5 @@ -0,0 +1,10 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "orderId": "c6f055d9-7f21-4079-913d-e6523a9cfffa", + "orderLinkId": "linear-004" + }, + "retExtInfo": {}, + "time": 1672217093461 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/getOrder.json5 b/xchange-bybit/src/test/resources/getOrder.json5 new file mode 100644 index 00000000000..f6078291770 --- /dev/null +++ b/xchange-bybit/src/test/resources/getOrder.json5 @@ -0,0 +1,55 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "list": [ + { + "orderId": "fd4300ae-7847-404e-b947-b46980a4d140", + "orderLinkId": "test-000005", + "blockTradeId": "", + "symbol": "ETHUSDT", + "price": "1600.00", + "qty": "0.10", + "side": "Buy", + "isLeverage": "", + "positionIdx": 1, + "orderStatus": "New", + "cancelType": "UNKNOWN", + "rejectReason": "EC_NoError", + "avgPrice": "0", + "leavesQty": "0.10", + "leavesValue": "160", + "cumExecQty": "0.00", + "cumExecValue": "0", + "cumExecFee": "0", + "timeInForce": "GTC", + "orderType": "Limit", + "stopOrderType": "UNKNOWN", + "orderIv": "", + "triggerPrice": "0.00", + "takeProfit": "2500.00", + "stopLoss": "1500.00", + "tpTriggerBy": "LastPrice", + "slTriggerBy": "LastPrice", + "triggerDirection": 0, + "triggerBy": "UNKNOWN", + "lastPriceOnCreated": "", + "reduceOnly": false, + "closeOnTrigger": false, + "smpType": "None", + "smpGroup": 0, + "smpOrderId": "", + "tpslMode": "Full", + "tpLimitPrice": "", + "slLimitPrice": "", + "placeType": "", + "createdTime": "1684738540559", + "updatedTime": "1684738540561" + } + ], + "nextPageCursor": "page_args%3Dfd4300ae-7847-404e-b947-b46980a4d140%26symbol%3D6%26", + "category": "linear" + }, + "retExtInfo": {}, + "time": 1684765770483 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/logback.xml b/xchange-bybit/src/test/resources/logback.xml index 2a586567a6e..da2d1b62c3c 100644 --- a/xchange-bybit/src/test/resources/logback.xml +++ b/xchange-bybit/src/test/resources/logback.xml @@ -17,7 +17,7 @@ - - + + \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/placeMarketOrder.json5 b/xchange-bybit/src/test/resources/placeMarketOrder.json5 new file mode 100644 index 00000000000..baa9a8b3efa --- /dev/null +++ b/xchange-bybit/src/test/resources/placeMarketOrder.json5 @@ -0,0 +1,10 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": { + "orderId": "1321003749386327552", + "orderLinkId": "spot-test-postonly" + }, + "retExtInfo": {}, + "time": 1672211918471 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/setLeverage.json5 b/xchange-bybit/src/test/resources/setLeverage.json5 new file mode 100644 index 00000000000..8244f328887 --- /dev/null +++ b/xchange-bybit/src/test/resources/setLeverage.json5 @@ -0,0 +1,7 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": {}, + "retExtInfo": {}, + "time": 1672281607343 +} \ No newline at end of file diff --git a/xchange-bybit/src/test/resources/switchPositionMode.json5 b/xchange-bybit/src/test/resources/switchPositionMode.json5 new file mode 100644 index 00000000000..5163634f16b --- /dev/null +++ b/xchange-bybit/src/test/resources/switchPositionMode.json5 @@ -0,0 +1,7 @@ +{ + "retCode": 0, + "retMsg": "OK", + "result": {}, + "retExtInfo": {}, + "time": 1675249072814 +} \ No newline at end of file diff --git a/xchange-stream-bybit/pom.xml b/xchange-stream-bybit/pom.xml index d52dd662f27..96144ca2b09 100644 --- a/xchange-stream-bybit/pom.xml +++ b/xchange-stream-bybit/pom.xml @@ -23,6 +23,12 @@ xchange-stream-core ${project.parent.version} + + + org.wiremock + wiremock + test + diff --git a/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexOrderChanges.java b/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexOrderChanges.java index 909d96eb21e..4a2b6e382ea 100644 --- a/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexOrderChanges.java +++ b/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexOrderChanges.java @@ -1,109 +1,157 @@ package dto.trade; -import com.fasterxml.jackson.annotation.JsonValue; import java.math.BigDecimal; import java.util.Date; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.Setter; +import lombok.ToString; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.trade.BybitOrderType; import org.knowm.xchange.bybit.dto.trade.BybitSide; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; import org.knowm.xchange.dto.Order; import org.knowm.xchange.instrument.Instrument; @Getter -@Setter +@ToString(callSuper = true) public class BybitComplexOrderChanges extends Order { - private BybitCategory category; - private BigDecimal price; - private BybitSide side; - private BigDecimal leavesQty; - private BigDecimal leavesValue; - private BigDecimal cumExecValue; - private String feeCurrency; - private TimeInForce timeInForce; - private BybitOrderType orderType; - private boolean reduceOnly; - private Date updatedTime; - public BybitComplexOrderChanges( - OrderType type, - BigDecimal originalAmount, - Instrument instrument, - String id, - Date timestamp, - BigDecimal averagePrice, - BigDecimal cumulativeAmount, - BigDecimal fee, - OrderStatus status, - String userReference) { - super( - type, - originalAmount, - instrument, - id, - timestamp, - averagePrice, - cumulativeAmount, - fee, - status, - userReference); - } + private final BybitCategory category; + private final String isLeverage; + private final String blockTradeId; + private final BigDecimal price; + private final BigDecimal qty; + private final BybitSide side; + private final int positionIdx; + private final String createType; + private final String cancelType; + private final String rejectReason; + private final BigDecimal leavesQty; + private final BigDecimal leavesValue; + private final BigDecimal cumExecValue; + private final String feeCurrency; + private final BybitTimeInForce timeInForce; + private final BybitOrderType orderType; + private final String stopOrderType; + private final String ocoTriggerBy; + private final String orderIv; + private final String marketUnit; + private final BigDecimal triggerPrice; + private final BigDecimal takeProfit; + private final BigDecimal stopLoss; + private final String tpslMode; + private final BigDecimal tpLimitPrice; + private final BigDecimal slLimitPrice; + private final String tpTriggerBy; + private final String slTriggerBy; + private final int triggerDirection; + private final String triggerBy; + private final String lastPriceOnCreated; + private final boolean reduceOnly; + private final boolean closeOnTrigger; + private final String placeType; + private final String smpType; + private final int smpGroup; + private final String smpOrderId; + private final Date updatedTime; public BybitComplexOrderChanges( OrderType type, BigDecimal originalAmount, Instrument instrument, - String id, - Date timestamp, + String orderLinkId, + Date createdTime, BigDecimal averagePrice, - BigDecimal cumulativeAmount, - BigDecimal fee, - OrderStatus status, - String userReference, + BigDecimal cumExecQty, + BigDecimal cumExecFee, + OrderStatus orderStatus, BybitCategory category, + String orderId, + String isLeverage, + String blockTradeId, BigDecimal price, + BigDecimal qty, BybitSide side, + int positionIdx, + String createType, + String cancelType, + String rejectReason, BigDecimal leavesQty, BigDecimal leavesValue, BigDecimal cumExecValue, String feeCurrency, - TimeInForce timeInForce, + BybitTimeInForce timeInForce, BybitOrderType orderType, + String stopOrderType, + String ocoTriggerBy, + String orderIv, + String marketUnit, + BigDecimal triggerPrice, + BigDecimal takeProfit, + BigDecimal stopLoss, + String tpslMode, + BigDecimal tpLimitPrice, + BigDecimal slLimitPrice, + String tpTriggerBy, + String slTriggerBy, + int triggerDirection, + String triggerBy, + String lastPriceOnCreated, boolean reduceOnly, + boolean closeOnTrigger, + String placeType, + String smpType, + int smpGroup, + String smpOrderId, Date updatedTime) { super( type, originalAmount, instrument, - id, - timestamp, + orderId, + createdTime, averagePrice, - cumulativeAmount, - fee, - status, - userReference); + cumExecQty, + cumExecFee, + orderStatus, + orderLinkId); this.category = category; + this.isLeverage = isLeverage; + this.blockTradeId = blockTradeId; this.price = price; + this.qty = qty; this.side = side; + this.positionIdx = positionIdx; + this.createType = createType; + this.cancelType = cancelType; + this.rejectReason = rejectReason; this.leavesQty = leavesQty; this.leavesValue = leavesValue; this.cumExecValue = cumExecValue; this.feeCurrency = feeCurrency; this.timeInForce = timeInForce; this.orderType = orderType; + this.stopOrderType = stopOrderType; + this.ocoTriggerBy = ocoTriggerBy; + this.orderIv = orderIv; + this.marketUnit = marketUnit; + this.triggerPrice = triggerPrice; + this.takeProfit = takeProfit; + this.stopLoss = stopLoss; + this.tpslMode = tpslMode; + this.tpLimitPrice = tpLimitPrice; + this.slLimitPrice = slLimitPrice; + this.tpTriggerBy = tpTriggerBy; + this.slTriggerBy = slTriggerBy; + this.triggerDirection = triggerDirection; + this.triggerBy = triggerBy; + this.lastPriceOnCreated = lastPriceOnCreated; this.reduceOnly = reduceOnly; + this.closeOnTrigger = closeOnTrigger; + this.placeType = placeType; + this.smpType = smpType; + this.smpGroup = smpGroup; + this.smpOrderId = smpOrderId; this.updatedTime = updatedTime; } - @Getter - @AllArgsConstructor - public enum TimeInForce { - GTC("GTC"), - IOC("IOC"), - FOK("FOK"), - POSTONLY("PostOnly"); - @JsonValue private final String value; - } } diff --git a/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexPositionChanges.java b/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexPositionChanges.java index 2081300805f..fab098edc9e 100644 --- a/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexPositionChanges.java +++ b/xchange-stream-bybit/src/main/java/dto/trade/BybitComplexPositionChanges.java @@ -1,21 +1,43 @@ package dto.trade; import java.math.BigDecimal; +import java.util.Date; import lombok.Getter; import lombok.Setter; +import lombok.ToString; import org.knowm.xchange.dto.account.OpenPosition; import org.knowm.xchange.instrument.Instrument; @Getter @Setter +@ToString(callSuper = true) public class BybitComplexPositionChanges extends OpenPosition { + + private int positionIdx; + private int tradeMode; + private int riskId; + private String riskLimitValue; + private BigDecimal markPrice; + private BigDecimal positionBalance; + private int autoAddMargin; + private BigDecimal positionMM; + private BigDecimal positionIM; + private BigDecimal bustPrice; private BigDecimal positionValue; private BigDecimal leverage; private BigDecimal takeProfit; private BigDecimal stopLoss; + private BigDecimal trailingStop; private BigDecimal curRealisedPnl; - private long createdTime; - private long updatedTime; + private BigDecimal cumRealisedPnl; + private BigDecimal sessionAvgPrice; // USDC contract session avg price + private String positionStatus; + private int adlRankIndicator; + private boolean isReduceOnly; + private String mmrSysUpdatedTime; + private String leverageSysUpdatedTime; + private Date createdTime; + private Date updatedTime; private long seq; public BybitComplexPositionChanges( @@ -30,8 +52,9 @@ public BybitComplexPositionChanges( BigDecimal takeProfit, BigDecimal stopLoss, BigDecimal curRealisedPnl, - long createdTime, - long updatedTime, + BigDecimal cumRealisedPnl, + Date createdTime, + Date updatedTime, long seq) { super(instrument, type, size, entryPrice, liquidationPrice, unRealisedPnl); this.positionValue = positionValue; @@ -39,6 +62,7 @@ public BybitComplexPositionChanges( this.takeProfit = takeProfit; this.stopLoss = stopLoss; this.curRealisedPnl = curRealisedPnl; + this.cumRealisedPnl = cumRealisedPnl; this.createdTime = createdTime; this.updatedTime = updatedTime; this.seq = seq; @@ -52,6 +76,11 @@ public BybitComplexPositionChanges( BigDecimal liquidationPrice, BigDecimal unRealisedPnl) { super(instrument, type, size, price, liquidationPrice, unRealisedPnl); + if (size.compareTo(BigDecimal.ZERO) != 0 && price.compareTo(BigDecimal.ZERO) != 0) { + this.positionValue = size.multiply(price); + } else { + this.positionValue = BigDecimal.ZERO; + } } public BybitComplexPositionChanges(BybitComplexPositionChanges changes) { @@ -62,13 +91,93 @@ public BybitComplexPositionChanges(BybitComplexPositionChanges changes) { changes.getPrice(), changes.getLiquidationPrice(), changes.getUnRealisedPnl()); + this.positionIdx = changes.positionIdx; + this.tradeMode = changes.tradeMode; + this.riskId = changes.riskId; + this.riskLimitValue = changes.riskLimitValue; + this.markPrice = changes.markPrice; + this.positionBalance = changes.positionBalance; + this.autoAddMargin = changes.autoAddMargin; + this.positionMM = changes.positionMM; + this.positionIM = changes.positionIM; + this.bustPrice = changes.bustPrice; this.positionValue = changes.positionValue; this.leverage = changes.leverage; this.takeProfit = changes.takeProfit; this.stopLoss = changes.stopLoss; + this.trailingStop = changes.trailingStop; this.curRealisedPnl = changes.curRealisedPnl; + this.cumRealisedPnl = changes.cumRealisedPnl; + this.sessionAvgPrice = changes.sessionAvgPrice; + this.positionStatus = changes.positionStatus; + this.adlRankIndicator = changes.adlRankIndicator; + this.isReduceOnly = changes.isReduceOnly; + this.mmrSysUpdatedTime = changes.mmrSysUpdatedTime; + this.leverageSysUpdatedTime = changes.leverageSysUpdatedTime; this.createdTime = changes.createdTime; this.updatedTime = changes.updatedTime; this.seq = changes.seq; } + + public BybitComplexPositionChanges( + Instrument instrument, + Type type, + BigDecimal size, + BigDecimal price, + BigDecimal liquidationPrice, + BigDecimal unRealisedPnl, + int positionIdx, + int tradeMode, + int riskId, + String riskLimitValue, + BigDecimal markPrice, + BigDecimal positionBalance, + int autoAddMargin, + BigDecimal positionMM, + BigDecimal positionIM, + BigDecimal bustPrice, + BigDecimal positionValue, + BigDecimal leverage, + BigDecimal takeProfit, + BigDecimal stopLoss, + BigDecimal trailingStop, + BigDecimal curRealisedPnl, + BigDecimal cumRealisedPnl, + BigDecimal sessionAvgPrice, + String positionStatus, + int adlRankIndicator, + boolean isReduceOnly, + String mmrSysUpdatedTime, + String leverageSysUpdatedTime, + Date createdTime, + Date updatedTime, + long seq) { + super(instrument, type, size, price, liquidationPrice, unRealisedPnl); + this.positionIdx = positionIdx; + this.tradeMode = tradeMode; + this.riskId = riskId; + this.riskLimitValue = riskLimitValue; + this.markPrice = markPrice; + this.positionBalance = positionBalance; + this.autoAddMargin = autoAddMargin; + this.positionMM = positionMM; + this.positionIM = positionIM; + this.bustPrice = bustPrice; + this.positionValue = positionValue; + this.leverage = leverage; + this.takeProfit = takeProfit; + this.stopLoss = stopLoss; + this.trailingStop = trailingStop; + this.curRealisedPnl = curRealisedPnl; + this.cumRealisedPnl = cumRealisedPnl; + this.sessionAvgPrice = sessionAvgPrice; + this.positionStatus = positionStatus; + this.adlRankIndicator = adlRankIndicator; + this.isReduceOnly = isReduceOnly; + this.mmrSysUpdatedTime = mmrSysUpdatedTime; + this.leverageSysUpdatedTime = leverageSysUpdatedTime; + this.createdTime = createdTime; + this.updatedTime = updatedTime; + this.seq = seq; + } } diff --git a/xchange-stream-bybit/src/main/java/dto/trade/BybitOrderChangesResponse.java b/xchange-stream-bybit/src/main/java/dto/trade/BybitOrderChangesResponse.java index 1981a60a9a7..5dfcdae9cef 100644 --- a/xchange-stream-bybit/src/main/java/dto/trade/BybitOrderChangesResponse.java +++ b/xchange-stream-bybit/src/main/java/dto/trade/BybitOrderChangesResponse.java @@ -1,7 +1,7 @@ package dto.trade; -import java.util.ArrayList; import java.util.List; +import lombok.AllArgsConstructor; import lombok.Getter; import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.bybit.dto.trade.BybitOrderStatus; @@ -14,7 +14,7 @@ public class BybitOrderChangesResponse { String id; String topic; long creationTime; - List data = new ArrayList<>(); + List data; @Getter public static class BybitOrderChanges { @@ -45,7 +45,7 @@ public static class BybitOrderChanges { private String stopOrderType; private String ocoTriggerBy; private String orderIv; - private String marketUnit; + private String marketUnit = ""; private String triggerPrice; private String takeProfit; private String stopLoss; @@ -56,13 +56,13 @@ public static class BybitOrderChanges { private String slTriggerBy; private int triggerDirection; private String triggerBy; - private String lastPriceOnCreated; + private String lastPriceOnCreated = ""; private boolean reduceOnly; private boolean closeOnTrigger; private String placeType; private String smpType; private int smpGroup; - private String smpOrderId; + private String smpOrderId = ""; private String createdTime; private String updatedTime; } diff --git a/xchange-stream-bybit/src/main/java/dto/trade/BybitPositionChangesResponse.java b/xchange-stream-bybit/src/main/java/dto/trade/BybitPositionChangesResponse.java index 437cb80149d..387c542e8d7 100644 --- a/xchange-stream-bybit/src/main/java/dto/trade/BybitPositionChangesResponse.java +++ b/xchange-stream-bybit/src/main/java/dto/trade/BybitPositionChangesResponse.java @@ -8,54 +8,51 @@ @Getter public class BybitPositionChangesResponse { - String id; - String topic; - long creationTime; - List data = new ArrayList<>(); + private String id; + private String topic; + private long creationTime; + private final List data = new ArrayList<>(); @Getter public static class BybitPositionChanges { - BybitCategory category; - String symbol; - String side; - String size; - int positionIdx; - String tradeMode; - String positionValue; - int riskId; - String riskLimitValue; - String entryPrice; - String markPrice; - String leverage; - String positionBalance; - int autoAddMargin; - String positionMM; - String positionIM; - String liqPrice; - String bustPrice; - String tpslMode; - String takeProfit; - String stopLoss; - String trailingStop; - String unrealisedPnl; - String curRealisedPnl; - String sessionAvgPrice; - String delta; - String gamma; - String vega; - String theta; - String cumRealisedPnl; - String positionStatus; - int adlRankIndicator; - boolean isReduceOnly; - - String mmrSysUpdatedTime; - - String leverageSysUpdatedTime; - - String createdTime; - String updatedTime; - long seq; + private BybitCategory category; + private String symbol; + private String side; + private String size; + private int positionIdx; + private int tradeMode; + private String positionValue; + private int riskId; + private String riskLimitValue; + private String entryPrice; + private String markPrice; + private String leverage; + private String positionBalance; + private int autoAddMargin; + private String positionMM; + private String positionIM; + private String liqPrice; + private String bustPrice; + private String tpslMode; + private String takeProfit; + private String stopLoss; + private String trailingStop; + private String unrealisedPnl; + private String curRealisedPnl; + private String sessionAvgPrice; + private String delta; + private String gamma; + private String vega; + private String theta; + private String cumRealisedPnl; + private String positionStatus; + private int adlRankIndicator; + private boolean isReduceOnly; + private String mmrSysUpdatedTime; + private String leverageSysUpdatedTime; + private String createdTime; + private String updatedTime; + private long seq; } } diff --git a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamAdapters.java b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamAdapters.java index bd02f94de63..3ebcae7f80c 100644 --- a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamAdapters.java +++ b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamAdapters.java @@ -1,13 +1,12 @@ package info.bitrich.xchangestream.bybit; import static org.knowm.xchange.bybit.BybitAdapters.adaptBybitOrderStatus; +import static org.knowm.xchange.bybit.BybitAdapters.convertBybitSymbolToInstrument; import static org.knowm.xchange.bybit.BybitAdapters.getOrderType; -import static org.knowm.xchange.bybit.BybitAdapters.guessSymbol; import dto.marketdata.BybitOrderbook; import dto.marketdata.BybitPublicOrder; import dto.trade.BybitComplexOrderChanges; -import dto.trade.BybitComplexOrderChanges.TimeInForce; import dto.trade.BybitComplexPositionChanges; import dto.trade.BybitOrderChangesResponse.BybitOrderChanges; import dto.trade.BybitPositionChangesResponse.BybitPositionChanges; @@ -16,6 +15,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.Order.OrderType; import org.knowm.xchange.dto.account.OpenPosition; @@ -31,18 +31,18 @@ public class BybitStreamAdapters { - public static OrderBook adaptOrderBook(BybitOrderbook bybitOrderbooks, Instrument instrument) { + public static OrderBook adaptOrderBook(BybitOrderbook bybitOrderBooks, Instrument instrument) { List asks = new ArrayList<>(); List bids = new ArrayList<>(); - Date timestamp = new Date(Long.parseLong(bybitOrderbooks.getTs())); - bybitOrderbooks + Date timestamp = new Date(Long.parseLong(bybitOrderBooks.getTs())); + bybitOrderBooks .getData() .getAsk() .forEach( bybitAsk -> asks.add(adaptOrderBookOrder(bybitAsk, instrument, OrderType.ASK, timestamp))); - bybitOrderbooks + bybitOrderBooks .getData() .getBid() .forEach( @@ -96,12 +96,15 @@ public static List adaptOrdersChanges(List bybitOrderC case LIMIT: builder = new LimitOrder.Builder( - orderType, - guessSymbol(bybitOrderChange.getSymbol(), bybitOrderChange.getCategory())) + orderType, + convertBybitSymbolToInstrument(bybitOrderChange.getSymbol(), + bybitOrderChange.getCategory())) .limitPrice(new BigDecimal(bybitOrderChange.getPrice())); break; case MARKET: - builder = new MarketOrder.Builder(orderType, guessSymbol(bybitOrderChange.getSymbol())); + builder = new MarketOrder.Builder(orderType, + convertBybitSymbolToInstrument(bybitOrderChange.getSymbol(), + bybitOrderChange.getCategory())); break; } if (!bybitOrderChange.getAvgPrice().isEmpty()) { @@ -109,7 +112,6 @@ public static List adaptOrdersChanges(List bybitOrderC } builder .fee(new BigDecimal(bybitOrderChange.getCumExecFee())) - .leverage(bybitOrderChange.getIsLeverage()) .id(bybitOrderChange.getOrderId()) .orderStatus(adaptBybitOrderStatus(bybitOrderChange.getOrderStatus())) .timestamp(date) @@ -126,17 +128,14 @@ public static OpenPositions adaptPositionChanges( List bybitPositionChanges) { OpenPositions openPositions = new OpenPositions(new ArrayList<>()); for (BybitPositionChanges position : bybitPositionChanges) { - OpenPosition.Type type = null; - if (!position.getSide().isEmpty()) { - type = position.getSide().equals("Buy") ? Type.LONG : Type.SHORT; - } - BigDecimal liqPrice = null; + OpenPosition.Type type = getPositionType(position); + BigDecimal liqPrice = getLiqPrice(position); if (!position.getLiqPrice().isEmpty()) { liqPrice = new BigDecimal(position.getLiqPrice()); } OpenPosition openPosition = new OpenPosition( - guessSymbol(position.getSymbol(), position.getCategory()), + convertBybitSymbolToInstrument(position.getSymbol(), position.getCategory()), type, new BigDecimal(position.getSize()), new BigDecimal(position.getEntryPrice()), @@ -147,33 +146,66 @@ public static OpenPositions adaptPositionChanges( return openPositions; } + private static OpenPosition.Type getPositionType(BybitPositionChanges position) { + if (!position.getSide().isEmpty()) { + return position.getSide().equals("Buy") ? Type.LONG : Type.SHORT; + } + return null; + } + private static BigDecimal getLiqPrice(BybitPositionChanges position) { + if (!position.getLiqPrice().isEmpty()) { + return new BigDecimal(position.getLiqPrice()); + } + return null; + } + public static List adaptComplexPositionChanges( List data) { List result = new ArrayList<>(); for (BybitPositionChanges position : data) { - OpenPosition.Type type = null; - if (!position.getSide().isEmpty()) { - type = position.getSide().equals("Buy") ? Type.LONG : Type.SHORT; + OpenPosition.Type type = getPositionType(position); + BigDecimal liqPrice = getLiqPrice(position); + BigDecimal bustPrice = null; + if (!position.getBustPrice().isEmpty()) { + bustPrice = new BigDecimal(position.getBustPrice()); } - BigDecimal liqPrice = null; - if (!position.getLiqPrice().isEmpty()) { - liqPrice = new BigDecimal(position.getLiqPrice()); + BigDecimal sessionAvgPrice = null; + if (!position.getSessionAvgPrice().isEmpty()) { + sessionAvgPrice = new BigDecimal(position.getSessionAvgPrice()); } BybitComplexPositionChanges positionChanges = new BybitComplexPositionChanges( - guessSymbol(position.getSymbol(), position.getCategory()), + convertBybitSymbolToInstrument(position.getSymbol(), position.getCategory()), type, new BigDecimal(position.getSize()), + new BigDecimal(position.getEntryPrice()), liqPrice, new BigDecimal(position.getUnrealisedPnl()), + position.getPositionIdx(), + position.getTradeMode(), + position.getRiskId(), + position.getRiskLimitValue(), + new BigDecimal(position.getMarkPrice()), + new BigDecimal(position.getPositionBalance()), + position.getAutoAddMargin(), + new BigDecimal(position.getPositionMM()), + new BigDecimal(position.getPositionIM()), + bustPrice, new BigDecimal(position.getPositionValue()), - new BigDecimal(position.getEntryPrice()), new BigDecimal(position.getLeverage()), new BigDecimal(position.getTakeProfit()), new BigDecimal(position.getStopLoss()), + new BigDecimal(position.getTrailingStop()), new BigDecimal(position.getCurRealisedPnl()), - Long.parseLong(position.getCreatedTime()), - Long.parseLong(position.getUpdatedTime()), + new BigDecimal(position.getCumRealisedPnl()), + sessionAvgPrice, + position.getPositionStatus(), + position.getAdlRankIndicator(), + position.isReduceOnly(), + position.getMmrSysUpdatedTime(), + position.getLeverageSysUpdatedTime(), + new Date(Long.parseLong(position.getCreatedTime())), + new Date(Long.parseLong(position.getUpdatedTime())), position.getSeq()); result.add(positionChanges); } @@ -184,32 +216,66 @@ public static List adaptComplexOrdersChanges( List data) { List result = new ArrayList<>(); for (BybitOrderChanges change : data) { - Order.OrderType orderType = getOrderType(change.getSide()); + Order.OrderType type = getOrderType(change.getSide()); BigDecimal avgPrice = change.getAvgPrice().isEmpty() ? null : new BigDecimal(change.getAvgPrice()); + BigDecimal triggerPrice = + change.getTriggerPrice().isEmpty() ? null : new BigDecimal(change.getTriggerPrice()); + BigDecimal takeProfit = + change.getTakeProfit().isEmpty() ? null : new BigDecimal(change.getTakeProfit()); + BigDecimal stopLoss = + change.getStopLoss().isEmpty() ? null : new BigDecimal(change.getStopLoss()); BybitComplexOrderChanges orderChanges = new BybitComplexOrderChanges( - orderType, + type, new BigDecimal(change.getQty()), - guessSymbol(change.getSymbol(), change.getCategory()), - change.getOrderId(), + convertBybitSymbolToInstrument(change.getSymbol(), change.getCategory()), + change.getOrderLinkId(), new Date(Long.parseLong(change.getCreatedTime())), avgPrice, new BigDecimal(change.getCumExecQty()), new BigDecimal(change.getCumExecFee()), adaptBybitOrderStatus(change.getOrderStatus()), - change.getOrderLinkId(), change.getCategory(), + change.getOrderId(), + change.getIsLeverage(), + change.getBlockTradeId(), new BigDecimal(change.getPrice()), + new BigDecimal(change.getQty()), change.getSide(), - new BigDecimal(change.getLeavesQty()), - new BigDecimal(change.getLeavesValue()), + change.getPositionIdx(), + change.getCreateType(), + change.getCancelType(), + change.getRejectReason(), + change.getLeavesQty().isEmpty() ? null : new BigDecimal(change.getLeavesQty()), + change.getLeavesValue().isEmpty() ? null : new BigDecimal(change.getLeavesValue()), new BigDecimal(change.getCumExecValue()), change.getFeeCurrency(), - TimeInForce.valueOf(change.getTimeInForce().toUpperCase()), + BybitTimeInForce.valueOf(change.getTimeInForce().toUpperCase()), change.getOrderType(), + change.getStopOrderType(), + change.getOcoTriggerBy(), + change.getOrderIv(), + change.getMarketUnit(), + triggerPrice, + takeProfit, + stopLoss, + change.getTpslMode(), + change.getTpLimitPrice().isEmpty() ? null : new BigDecimal(change.getTpLimitPrice()), + change.getSlLimitPrice().isEmpty() ? null : new BigDecimal(change.getSlLimitPrice()), + change.getTpTriggerBy(), + change.getSlTriggerBy(), + change.getTriggerDirection(), + change.getTriggerBy(), + change.getLastPriceOnCreated(), change.isReduceOnly(), + change.isCloseOnTrigger(), + change.getPlaceType(), + change.getSmpType(), + change.getSmpGroup(), + change.getSmpOrderId(), new Date(Long.parseLong(change.getUpdatedTime()))); + result.add(orderChanges); } return result; diff --git a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java index 15bd1e2a469..c9fe13147e2 100644 --- a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java +++ b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java @@ -15,8 +15,7 @@ public class BybitStreamingExchange extends BybitExchange implements StreamingEx // https://bybit-exchange.github.io/docs/v5/ws/connect public static final String URI = "wss://stream.bybit.com/v5/public"; public static final String TESTNET_URI = "wss://stream-testnet.bybit.com/v5/public"; - // DEMO_URI websocket not worked(401 error) - public static final String DEMO_URI = "wss://stream-demo.bybit.com/v5/public"; + // DEMO_URI without auth is the same as URI public static final String AUTH_URI = "wss://stream.bybit.com/v5/private"; public static final String TESTNET_AUTH_URI = "wss://stream-testnet.bybit.com/v5/private"; @@ -41,22 +40,24 @@ private String getApiUrl() { String apiUrl; if (exchangeSpecification.getApiKey() == null) { if (Boolean.TRUE.equals( - exchangeSpecification.getExchangeSpecificParametersItem(USE_SANDBOX))) { + exchangeSpecification.getExchangeSpecificParametersItem(SPECIFIC_PARAM_TESTNET))) { apiUrl = TESTNET_URI; } else { apiUrl = URI; } - apiUrl += - "/" - + ((BybitCategory) - exchangeSpecification.getExchangeSpecificParametersItem(EXCHANGE_TYPE)) - .getValue(); + apiUrl += "/" + ((BybitCategory) exchangeSpecification.getExchangeSpecificParametersItem( + EXCHANGE_TYPE)).getValue(); } else { if (Boolean.TRUE.equals( exchangeSpecification.getExchangeSpecificParametersItem(USE_SANDBOX))) { - apiUrl = TESTNET_AUTH_URI; + apiUrl = DEMO_AUTH_URI; } else { - apiUrl = AUTH_URI; + if (Boolean.TRUE.equals( + exchangeSpecification.getExchangeSpecificParametersItem(SPECIFIC_PARAM_TESTNET))) { + apiUrl = TESTNET_AUTH_URI; + } else { + apiUrl = AUTH_URI; + } } } return apiUrl; @@ -93,4 +94,5 @@ public BybitStreamingMarketDataService getStreamingMarketDataService() { public BybitStreamingTradeService getStreamingTradeService() { return streamingTradeService; } + } diff --git a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingMarketDataService.java b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingMarketDataService.java index 21ad1dd6d59..8ed3f60f1d4 100644 --- a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingMarketDataService.java +++ b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingMarketDataService.java @@ -64,17 +64,17 @@ public Observable getOrderBook(Instrument instrument, Object... args) .subscribeChannel(channelUniqueId) .flatMap( jsonNode -> { - BybitOrderbook bybitOrderbooks = mapper.treeToValue(jsonNode, BybitOrderbook.class); - String type = bybitOrderbooks.getDataType(); + BybitOrderbook bybitOrderBooks = mapper.treeToValue(jsonNode, BybitOrderbook.class); + String type = bybitOrderBooks.getDataType(); if (type.equalsIgnoreCase("snapshot")) { OrderBook orderBook = - BybitStreamAdapters.adaptOrderBook(bybitOrderbooks, instrument); - orderBookUpdateIdPrev.set(bybitOrderbooks.getData().getU()); + BybitStreamAdapters.adaptOrderBook(bybitOrderBooks, instrument); + orderBookUpdateIdPrev.set(bybitOrderBooks.getData().getU()); orderBookMap.put(channelUniqueId, orderBook); return Observable.just(orderBook); } else if (type.equalsIgnoreCase("delta")) { return applyDeltaSnapshot( - channelUniqueId, instrument, bybitOrderbooks, orderBookUpdateIdPrev); + channelUniqueId, instrument, bybitOrderBooks, orderBookUpdateIdPrev); } return Observable.fromIterable(new LinkedList<>()); }); diff --git a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingTradeService.java b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingTradeService.java index 8209a5c8267..a7521662635 100644 --- a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingTradeService.java +++ b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingTradeService.java @@ -11,6 +11,7 @@ import org.knowm.xchange.bybit.dto.BybitCategory; import org.knowm.xchange.dto.Order; import org.knowm.xchange.dto.account.OpenPosition; +import org.knowm.xchange.instrument.Instrument; public class BybitStreamingTradeService implements StreamingTradeService { @@ -21,10 +22,15 @@ public BybitStreamingTradeService(BybitStreamingService streamingService) { this.streamingService = streamingService; } - public Observable getOrderChanges(BybitCategory category) { + @Override + /* + * instrument param is not used + * arg[0] BybitCategory, if null then subscribe to all category + */ + public Observable getOrderChanges(Instrument instrument, Object... args) { String channelUniqueId = "order"; - if (category != null) { - channelUniqueId += "." + category.getValue(); + if(args[0] != null && args[0] instanceof BybitCategory) { + channelUniqueId += "." + ((BybitCategory)args[0]).getValue(); } return streamingService .subscribeChannel(channelUniqueId) diff --git a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamAdaptersTest.java b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamAdaptersTest.java new file mode 100644 index 00000000000..d92ea0c7066 --- /dev/null +++ b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamAdaptersTest.java @@ -0,0 +1,240 @@ +package info.bitrich.xchangestream.bybit; + +import static info.bitrich.xchangestream.bybit.BybitStreamAdapters.adaptComplexOrdersChanges; +import static info.bitrich.xchangestream.bybit.BybitStreamAdapters.adaptOrderBook; +import static info.bitrich.xchangestream.bybit.BybitStreamAdapters.adaptOrdersChanges; +import static info.bitrich.xchangestream.bybit.BybitStreamAdapters.adaptTrades; +import static org.assertj.core.api.Assertions.assertThat; +import static org.knowm.xchange.bybit.dto.BybitCategory.OPTION; +import static org.knowm.xchange.bybit.dto.trade.BybitOrderType.MARKET; +import static org.knowm.xchange.bybit.dto.trade.details.BybitTimeInForce.IOC; +import static org.knowm.xchange.dto.Order.OrderStatus.FILLED; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import dto.marketdata.BybitOrderbook; +import dto.trade.BybitComplexOrderChanges; +import dto.trade.BybitComplexPositionChanges; +import dto.trade.BybitOrderChangesResponse; +import dto.trade.BybitOrderChangesResponse.BybitOrderChanges; +import dto.trade.BybitPositionChangesResponse; +import dto.trade.BybitPositionChangesResponse.BybitPositionChanges; +import dto.trade.BybitTrade; +import info.bitrich.xchangestream.service.netty.StreamingObjectMapperHelper; +import io.reactivex.rxjava3.observers.TestObserver; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.YearMonth; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.util.List; +import java.util.Locale; +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.derivative.OptionsContract; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.account.OpenPosition; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.marketdata.Trades; +import org.knowm.xchange.dto.trade.MarketOrder; + +public class BybitStreamAdaptersTest { + + ObjectMapper mapper = StreamingObjectMapperHelper.getObjectMapper(); + + @Test + public void adaptOrderBookTest() throws Exception { + JsonNode jsonNode = + mapper.readTree( + ClassLoader.getSystemClassLoader() + .getResourceAsStream("orderBookSnapshotResponse.json5")); + BybitOrderbook bybitOrderbookSnapshot = mapper.treeToValue(jsonNode, BybitOrderbook.class); + + OrderBook orderBook = adaptOrderBook(bybitOrderbookSnapshot, + new FuturesContract("BTC/USDT/PERP")); + assertThat(orderBook.getTimeStamp().getTime()).isEqualTo(1672304484978L); + + assertThat(orderBook.getBids().get(0).getLimitPrice()).isEqualTo(new BigDecimal("16493.50")); + assertThat(orderBook.getBids().get(0).getOriginalAmount()).isEqualTo(new BigDecimal("0.006")); + + assertThat(orderBook.getAsks().get(0).getLimitPrice()).isEqualTo(new BigDecimal("16611.00")); + assertThat(orderBook.getAsks().get(0).getOriginalAmount()).isEqualTo(new BigDecimal("0.029")); + } + + @Test + public void adaptTradesTest() throws Exception { + JsonNode jsonNode = + mapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("tradeResponse.json5")); + List bybitTradeList = mapper.treeToValue(jsonNode.get("data"), + mapper.getTypeFactory() + .constructCollectionType(List.class, BybitTrade.class)); + Trades trades = adaptTrades(bybitTradeList, new FuturesContract("BTC/USDT/PERP")); + assertThat(trades.getTrades().get(0).getId()).isEqualTo("20f43950-d8dd-5b31-9112-a178eb6023af"); + assertThat(trades.getTrades().get(0).getTimestamp().getTime()).isEqualTo(1672304486865L); + assertThat(trades.getTrades().get(0).getInstrument()).isEqualTo( + new FuturesContract("BTC/USDT/PERP")); + assertThat(trades.getTrades().get(0).getType()).isEqualTo(OrderType.BID); + assertThat(trades.getTrades().get(0).getOriginalAmount()).isEqualTo(new BigDecimal("0.001")); + assertThat(trades.getTrades().get(0).getPrice()).isEqualTo(new BigDecimal("16578.50")); + } + + @Test + public void adaptOrdersChangesTest() throws Exception { + JsonNode jsonNode = + mapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("orderChangeResponse.json5")); + BybitOrderChangesResponse bybitOrderChangesResponse = mapper.treeToValue(jsonNode, + BybitOrderChangesResponse.class); + DateTimeFormatter dateParser = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("ddLLLyy") + .toFormatter(Locale.US); + Order order = adaptOrdersChanges(bybitOrderChangesResponse.getData()).get(0); + assertThat(order.getInstrument()).isEqualTo(new OptionsContract("ETH/USDC/221230/1400/C")); + assertThat(order.getId()).isEqualTo("5cf98598-39a7-459e-97bf-76ca765ee020"); + assertThat(order.getType()).isEqualTo(OrderType.ASK); + assertThat(order instanceof MarketOrder).isEqualTo(true); + assert order instanceof MarketOrder; + assertThat(order.getAveragePrice()).isEqualTo(new BigDecimal("75")); + assertThat(order.getOriginalAmount()).isEqualTo(new BigDecimal("1")); + //here it's updated time, because Order don't have other field + assertThat(order.getTimestamp().getTime()).isEqualTo(1672364262457L); + assertThat(order.getCumulativeAmount()).isEqualTo(new BigDecimal("1")); + assertThat(order.getFee()).isEqualTo(new BigDecimal("0.358635")); + assertThat(order.getStatus()).isEqualTo(FILLED); + assertThat(order.getLeverage()).isEqualTo(null); + } + + @Test + public void adaptPositionChanges() throws Exception { + JsonNode jsonNode = + mapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("positionChangeResponse.json5")); + BybitPositionChangesResponse bybitPositionChangesResponse = + mapper.treeToValue(jsonNode, BybitPositionChangesResponse.class); + List openPositions = BybitStreamAdapters.adaptPositionChanges( + bybitPositionChangesResponse.getData()).getOpenPositions(); + assertThat(openPositions.size()).isEqualTo(1); + assertThat(openPositions.get(0).getInstrument()).isEqualTo( + new FuturesContract("BTC/USDT/PERP")); + assertThat(openPositions.get(0).getLiquidationPrice()).isEqualTo(BigDecimal.ZERO); + assertThat(openPositions.get(0).getPrice()).isEqualTo(BigDecimal.ZERO); + assertThat(openPositions.get(0).getSize()).isEqualTo(BigDecimal.ZERO); + assertThat(openPositions.get(0).getType()).isEqualTo(null); + assertThat(openPositions.get(0).getUnRealisedPnl()).isEqualTo(BigDecimal.ZERO); + } + + @Test + public void adaptComplexPositionChanges() throws Exception { + JsonNode jsonNode = + mapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("positionChangeResponse.json5")); + BybitPositionChangesResponse bybitPositionChangesResponse = + mapper.treeToValue(jsonNode, BybitPositionChangesResponse.class); + List bybitPositionChanges = BybitStreamAdapters.adaptComplexPositionChanges( + bybitPositionChangesResponse.getData()); + assertThat(bybitPositionChanges.size()).isEqualTo(1); + assertThat(bybitPositionChanges.get(0).getInstrument()).isEqualTo( + new FuturesContract("BTC/USDT/PERP")); + assertThat(bybitPositionChanges.get(0).getLiquidationPrice()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getPrice()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getSize()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getType()).isEqualTo(null); + assertThat(bybitPositionChanges.get(0).getUnRealisedPnl()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getPositionIdx()).isEqualTo(2); + assertThat(bybitPositionChanges.get(0).getTradeMode()).isEqualTo(0); + assertThat(bybitPositionChanges.get(0).getRiskId()).isEqualTo(1); + assertThat(bybitPositionChanges.get(0).getRiskLimitValue()).isEqualTo("2000000"); + assertThat(bybitPositionChanges.get(0).getLeverage()).isEqualTo(new BigDecimal("10")); + assertThat(bybitPositionChanges.get(0).getPositionValue()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getPositionBalance()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getMarkPrice()).isEqualTo(new BigDecimal("28184.5")); + assertThat(bybitPositionChanges.get(0).getPositionIM()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getPositionMM()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getTakeProfit()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getStopLoss()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getTrailingStop()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getCurRealisedPnl()).isEqualTo(new BigDecimal("1.26")); + assertThat(bybitPositionChanges.get(0).getCumRealisedPnl()).isEqualTo(new BigDecimal("-25.06579337")); + assertThat(bybitPositionChanges.get(0).getSessionAvgPrice()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getCreatedTime().getTime()).isEqualTo(1694402496913L); + assertThat(bybitPositionChanges.get(0).getUpdatedTime().getTime()).isEqualTo(1697682317038L); + assertThat(bybitPositionChanges.get(0).getLiquidationPrice()).isEqualTo(BigDecimal.ZERO); + assertThat(bybitPositionChanges.get(0).getBustPrice()==null).isTrue(); + assertThat(bybitPositionChanges.get(0).getPositionStatus()).isEqualTo("Normal"); + assertThat(bybitPositionChanges.get(0).getAdlRankIndicator()).isEqualTo(0); + assertThat(bybitPositionChanges.get(0).getAutoAddMargin()).isEqualTo(0); + assertThat(bybitPositionChanges.get(0).getLeverageSysUpdatedTime()).isEmpty(); + assertThat(bybitPositionChanges.get(0).getMmrSysUpdatedTime()).isEmpty(); + assertThat(bybitPositionChanges.get(0).getSeq()).isEqualTo(8327597863L); + assertThat(bybitPositionChanges.get(0).isReduceOnly()).isEqualTo(false); + } + + @Test + public void adaptComplexOrdersChangesTest() throws Exception { + JsonNode jsonNode = + mapper.readTree( + ClassLoader.getSystemClassLoader().getResourceAsStream("orderChangeResponse.json5")); + BybitOrderChangesResponse bybitOrderChangesResponse = mapper.treeToValue(jsonNode, + BybitOrderChangesResponse.class); + DateTimeFormatter dateParser = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .appendPattern("ddLLLyy") + .toFormatter(Locale.US); + BybitComplexOrderChanges order = adaptComplexOrdersChanges( + bybitOrderChangesResponse.getData()).get(0); + assertThat(order.getInstrument()).isEqualTo(new OptionsContract("ETH/USDC/221230/1400/C")); + assertThat(order.getId()).isEqualTo("5cf98598-39a7-459e-97bf-76ca765ee020"); + assertThat(order.getType()).isEqualTo(OrderType.ASK); + + assertThat(order.getAveragePrice()).isEqualTo(new BigDecimal("75")); + assertThat(order.getOriginalAmount()).isEqualTo(new BigDecimal("1")); + // here it's create time + assertThat(order.getTimestamp().getTime()).isEqualTo(1672364262444L); + assertThat(order.getCumulativeAmount()).isEqualTo(new BigDecimal("1")); + assertThat(order.getFee()).isEqualTo(new BigDecimal("0.358635")); + assertThat(order.getStatus()).isEqualTo(FILLED); + assertThat(order.getLeverage()).isEqualTo(null); + assertThat(order.getCategory()).isEqualTo(OPTION); + assertThat(order.getBlockTradeId()).isEmpty(); + assertThat(order.getPositionIdx()).isEqualTo(0); + assertThat(order.getCreateType()).isEqualTo(null); + assertThat(order.getCancelType()).isEqualTo("UNKNOWN"); + assertThat(order.getRejectReason()).isEqualTo("EC_NoError"); + assertThat(order.getLeavesQty() == null).isTrue(); + assertThat(order.getLeavesValue() == null).isTrue(); + assertThat(order.getCumExecValue()).isEqualTo(new BigDecimal("75")); + assertThat(order.getFeeCurrency()).isEmpty(); + assertThat(order.getTimeInForce()).isEqualTo(IOC); + assertThat(order.getOrderType()).isEqualTo(MARKET); + assertThat(order.getStopOrderType()).isEmpty(); + assertThat(order.getOcoTriggerBy()).isEqualTo(null); + assertThat(order.getOrderIv()).isEmpty(); + assertThat(order.getMarketUnit()).isEmpty(); + assertThat(order.getTriggerPrice() == null).isTrue(); + assertThat(order.getTakeProfit() == null).isTrue(); + assertThat(order.getStopLoss() == null).isTrue(); + assertThat(order.getTpslMode()).isEmpty(); + assertThat(order.getTpLimitPrice() == null).isTrue(); + assertThat(order.getSlLimitPrice() == null).isTrue(); + assertThat(order.getTpTriggerBy()).isEmpty(); + assertThat(order.getSlTriggerBy()).isEmpty(); + assertThat(order.getTriggerDirection()).isEqualTo(0); + assertThat(order.getTriggerBy()).isEmpty(); + assertThat(order.getLastPriceOnCreated()).isEmpty(); + assertThat(order.isReduceOnly()).isEqualTo(false); + assertThat(order.isCloseOnTrigger()).isEqualTo(false); + assertThat(order.getPlaceType()).isEqualTo("price"); + assertThat(order.getSmpType()).isEqualTo("None"); + assertThat(order.getSmpGroup()).isEqualTo(0); + assertThat(order.getSmpOrderId()).isEmpty(); + assertThat(order.getUpdatedTime().getTime()).isEqualTo(1672364262457L); + } +} diff --git a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamExample.java b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamExample.java deleted file mode 100644 index a6cea09d71d..00000000000 --- a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/BybitStreamExample.java +++ /dev/null @@ -1,235 +0,0 @@ -package info.bitrich.xchangestream.bybit; - -import static java.math.RoundingMode.UP; -import static org.knowm.xchange.Exchange.USE_SANDBOX; -import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; - -import info.bitrich.xchangestream.core.StreamingExchange; -import info.bitrich.xchangestream.core.StreamingExchangeFactory; -import io.reactivex.rxjava3.disposables.Disposable; -import java.io.IOException; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import org.knowm.xchange.ExchangeSpecification; -import org.knowm.xchange.bybit.dto.BybitCategory; -import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; -import org.knowm.xchange.currency.CurrencyPair; -import org.knowm.xchange.derivative.FuturesContract; -import org.knowm.xchange.derivative.OptionsContract; -import org.knowm.xchange.dto.Order; -import org.knowm.xchange.dto.Order.OrderType; -import org.knowm.xchange.dto.marketdata.Ticker; -import org.knowm.xchange.dto.trade.LimitOrder; -import org.knowm.xchange.instrument.Instrument; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class BybitStreamExample { - private static final Logger log = LoggerFactory.getLogger(BybitStreamExample.class); - - public static void main(String[] args) { - try { - // spot(); - auth(); - } catch (IOException | InterruptedException e) { - throw new RuntimeException(e); - } - // futures(); - - } - - private static final Instrument BTC_PERP = new FuturesContract("BTC/USDT/PERP"); - private static final Instrument DOGE_PERP = new FuturesContract("DOGE/USDT/PERP"); - private static final Instrument ETH_PERP = new CurrencyPair("ETH/USDT/PERP"); - private static final Instrument BTC_SPOT = new CurrencyPair("BTC/USDT"); - private static final Instrument ETH_SPOT = new CurrencyPair("ETH/USDT"); - - private static void auth() throws IOException, InterruptedException { - ExchangeSpecification exchangeSpecification = - new BybitStreamingExchange().getDefaultExchangeSpecification(); - exchangeSpecification.setApiKey(System.getProperty("test_api_key")); - exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); - exchangeSpecification.setExchangeSpecificParametersItem( - SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); - exchangeSpecification.setExchangeSpecificParametersItem( - BybitStreamingExchange.EXCHANGE_TYPE, BybitCategory.LINEAR); - exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); - StreamingExchange exchange = - StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - exchange.connect().blockingAwait(); - Ticker ticker = (exchange.getMarketDataService().getTicker(DOGE_PERP)); - BigDecimal amount = - exchange.getExchangeMetaData().getInstruments().get(DOGE_PERP).getMinimumAmount(); - if (amount.multiply(ticker.getLast()).compareTo(new BigDecimal("5.0")) <= 0) { - amount = - new BigDecimal("5") - .divide( - ticker.getAsk(), - exchange.getExchangeMetaData().getInstruments().get(DOGE_PERP).getVolumeScale(), - UP); - } - LimitOrder limitOrder = - new LimitOrder(OrderType.BID, amount, DOGE_PERP, "", new Date(), ticker.getAsk()); - Thread.sleep(2000L); - AtomicReference order = new AtomicReference<>(); - Disposable disposableOrderChanges = - ((BybitStreamingTradeService) exchange.getStreamingTradeService()) - .getOrderChanges(BybitCategory.LINEAR) - .doOnError(error -> log.error("OrderChanges error {}", error.getMessage())) - .subscribe( - c -> { - log.info("Order Changes {}", c); - order.set(c); - }, - throwable -> log.error("OrderChanges throwable,{}", throwable.getMessage())); - // Disposable disposablePositionChanges = - // ((BybitStreamingTradeService)exchange.getStreamingTradeService()).getPositionChanges(BybitCategory.LINEAR) - // .doOnError( - // error -> log.error("PositionChanges error {}",error.getMessage())) - // .subscribe( p -> log.info("PositionChanges Changes {}", p), - // throwable -> log.error("Position throwable,{}",throwable.getMessage())); - - Disposable disposableComplexPositionChanges = - ((BybitStreamingTradeService) exchange.getStreamingTradeService()) - .getBybitPositionChanges(BybitCategory.LINEAR) - .doOnError(error -> log.error("ComplexPositionChanges error {}", error.getMessage())) - .subscribe( - p -> log.info("ComplexPositionChanges Changes {}", p), - throwable -> log.error("ComplexPosition throwable,{}", throwable.getMessage())); - - Thread.sleep(3000L); - exchange.getTradeService().placeLimitOrder(limitOrder); - Thread.sleep(30000L); - disposableOrderChanges.dispose(); - // disposablePositionChanges.dispose(); - disposableComplexPositionChanges.dispose(); - exchange.disconnect().blockingAwait(); - } - - public static void spot() throws IOException { - ExchangeSpecification exchangeSpecification = - new ExchangeSpecification(BybitStreamingExchange.class); - exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); - exchangeSpecification.setExchangeSpecificParametersItem( - BybitStreamingExchange.EXCHANGE_TYPE, BybitCategory.SPOT); - exchangeSpecification.setExchangeSpecificParametersItem( - SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); - StreamingExchange exchange = - StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - exchange.connect().blockingAwait(); - int count_currencyPair = 0; - int count_futureContractPerp = 0; - int count_futureContractDate = 0; - List instruments = - new ArrayList<>(exchange.getExchangeMetaData().getInstruments().keySet()); - for (Instrument instrument : instruments) { - if (instrument instanceof CurrencyPair) { - count_currencyPair++; - } - if (instrument instanceof FuturesContract) { - count_futureContractPerp++; - } - if (instrument instanceof OptionsContract) { - count_futureContractDate++; - } - } - System.out.println("Currency pairs: " + count_currencyPair); - System.out.println("Futures: " + count_futureContractPerp); - System.out.println("Futures date: " + count_futureContractDate); - - System.out.println(exchange.getMarketDataService().getTicker(BTC_SPOT)); - System.out.println(exchange.getMarketDataService().getTicker(BTC_PERP)); - List tradesDisposable = new ArrayList<>(); - Disposable bookDisposable = - exchange.getStreamingMarketDataService().getOrderBook(ETH_SPOT).subscribe(); - tradesDisposable.add( - exchange - .getStreamingMarketDataService() - .getTrades(ETH_SPOT) - .subscribe(trade -> System.out.println("trade: " + trade))); - tradesDisposable.add( - exchange - .getStreamingMarketDataService() - .getTrades(BTC_SPOT) - .subscribe(trade -> System.out.println("trade: " + trade))); - try { - Thread.sleep(2000); - } catch (InterruptedException ignored) { - } - bookDisposable.dispose(); - for (Disposable disposable : tradesDisposable) { - disposable.dispose(); - } - - try { - Thread.sleep(5000); - } catch (InterruptedException ignored) { - } - exchange.disconnect().blockingAwait(); - } - - public static void futures() { - ExchangeSpecification exchangeSpecification = - new ExchangeSpecification(BybitStreamingExchange.class); - exchangeSpecification.setExchangeSpecificParametersItem( - SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); - exchangeSpecification.setExchangeSpecificParametersItem( - BybitStreamingExchange.EXCHANGE_TYPE, BybitCategory.LINEAR); - exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); - StreamingExchange exchange = - StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); - exchange.connect().blockingAwait(); - List tradesDisposable = new ArrayList<>(); - List booksDisposable = new ArrayList<>(); - List booksUpdatesDisposable = new ArrayList<>(); - booksDisposable.add( - exchange.getStreamingMarketDataService().getOrderBook(BTC_PERP).subscribe()); - booksDisposable.add( - exchange.getStreamingMarketDataService().getOrderBook(ETH_PERP).subscribe()); - booksUpdatesDisposable.add( - exchange - .getStreamingMarketDataService() - .getOrderBookUpdates(BTC_PERP) - .subscribe( - orderBookUpdates -> System.out.printf("orderBookUpdates: %s\n", orderBookUpdates))); - booksUpdatesDisposable.add( - exchange - .getStreamingMarketDataService() - .getOrderBookUpdates(ETH_PERP) - .subscribe( - orderBookUpdates -> System.out.printf("orderBookUpdates: %s\n", orderBookUpdates))); - tradesDisposable.add( - exchange - .getStreamingMarketDataService() - .getTrades(BTC_PERP) - .subscribe(trade -> System.out.println("trade: " + trade))); - tradesDisposable.add( - exchange - .getStreamingMarketDataService() - .getTrades(ETH_PERP) - .subscribe(trade -> System.out.println("trade: " + trade))); - try { - Thread.sleep(2000); - } catch (InterruptedException ignored) { - - } - for (Disposable disposable : booksUpdatesDisposable) { - disposable.dispose(); - } - for (Disposable disposable : booksDisposable) { - disposable.dispose(); - } - for (Disposable disposable : tradesDisposable) { - disposable.dispose(); - } - try { - Thread.sleep(5000); - } catch (InterruptedException ignored) { - - } - exchange.disconnect().blockingAwait(); - } -} diff --git a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BaseBybitExchange.java b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BaseBybitExchange.java new file mode 100644 index 00000000000..e789b179309 --- /dev/null +++ b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BaseBybitExchange.java @@ -0,0 +1,31 @@ +package info.bitrich.xchangestream.bybit.example; + +import static org.knowm.xchange.Exchange.USE_SANDBOX; +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; + +import info.bitrich.xchangestream.bybit.BybitStreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; + +public class BaseBybitExchange { + public static StreamingExchange connect(BybitCategory category, boolean withAuth) { + ExchangeSpecification exchangeSpecification = + new BybitStreamingExchange().getDefaultExchangeSpecification(); + if(withAuth) { + exchangeSpecification.setApiKey(System.getProperty("test_api_key")); + exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); + } + exchangeSpecification.setExchangeSpecificParametersItem( + SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); + exchangeSpecification.setExchangeSpecificParametersItem( + BybitStreamingExchange.EXCHANGE_TYPE, category); + exchangeSpecification.setExchangeSpecificParametersItem(USE_SANDBOX, true); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + exchange.connect().blockingAwait(); + return exchange; + } +} diff --git a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamOrderBookExample.java b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamOrderBookExample.java new file mode 100644 index 00000000000..d48181599f7 --- /dev/null +++ b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamOrderBookExample.java @@ -0,0 +1,58 @@ +package info.bitrich.xchangestream.bybit.example; + +import static info.bitrich.xchangestream.bybit.example.BaseBybitExchange.connect; + +import info.bitrich.xchangestream.core.StreamingExchange; +import io.reactivex.rxjava3.disposables.Disposable; +import java.util.ArrayList; +import java.util.List; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.instrument.Instrument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BybitStreamOrderBookExample { + private static final Logger log = LoggerFactory.getLogger(BybitStreamOrderBookExample.class); + public static void main(String[] args) { + // Stream orderBook and OrderBookUpdates + try { + getOrderBookExample(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private static void getOrderBookExample() throws InterruptedException { + Instrument BTC_SPOT = new CurrencyPair("BTC/USDT"); + StreamingExchange exchange = connect(BybitCategory.SPOT, false); + List booksDisposable = new ArrayList<>(); + List booksUpdatesDisposable = new ArrayList<>(); + + booksDisposable.add( + exchange.getStreamingMarketDataService().getOrderBook(BTC_SPOT) + .doOnError( + error -> { + log.error(error.getMessage()); + }) + .subscribe( + orderBook -> log.info("orderBook: {}", orderBook.getTimeStamp()))); + + booksUpdatesDisposable.add( + exchange + .getStreamingMarketDataService() + .getOrderBookUpdates(BTC_SPOT) + .doOnError( + error -> { + log.error(error.getMessage()); + }) + .subscribe( + orderBookUpdates -> log.info("orderBookUpdates: {}", orderBookUpdates))); + Thread.sleep(4000L); + for (Disposable dis:booksDisposable) + dis.dispose(); + for (Disposable dis:booksUpdatesDisposable) + dis.dispose(); + exchange.disconnect().blockingAwait(); + } +} diff --git a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamPositionChangeExample.java b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamPositionChangeExample.java new file mode 100644 index 00000000000..288e9b4b80f --- /dev/null +++ b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamPositionChangeExample.java @@ -0,0 +1,90 @@ +package info.bitrich.xchangestream.bybit.example; + +import static info.bitrich.xchangestream.bybit.example.BaseBybitExchange.connect; +import static java.math.RoundingMode.UP; + +import info.bitrich.xchangestream.bybit.BybitStreamingTradeService; +import info.bitrich.xchangestream.core.StreamingExchange; +import io.reactivex.rxjava3.disposables.Disposable; +import java.io.IOException; +import java.math.BigDecimal; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.service.BybitAccountService; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.instrument.Instrument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BybitStreamPositionChangeExample { + + private static final Logger log = LoggerFactory.getLogger(BybitStreamPositionChangeExample.class); + + public static void main(String[] args) { + try { + // Stream positionChange and trade example + positionChangeExample(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static final Instrument ETH = new CurrencyPair("ETH/USDT"); + private static final Instrument ETH_PERP = new FuturesContract("ETH/USDT/PERP"); + static Ticker ticker; + static BigDecimal amount; + + + private static void positionChangeExample() throws IOException { + StreamingExchange exchange = connect(BybitCategory.LINEAR, true); + ticker = (exchange.getMarketDataService().getTicker(ETH_PERP)); + amount = exchange.getExchangeMetaData().getInstruments().get(ETH_PERP).getMinimumAmount(); + //minimal trade size - 5 USDT + if (amount.multiply(ticker.getBid()).compareTo(new BigDecimal("5.0")) <= 0) { + amount = + new BigDecimal("5") + .divide( + ticker.getBid(), + exchange.getExchangeMetaData().getInstruments().get(ETH_PERP).getVolumeScale(), + UP); + } + BybitAccountService bybitAccountService = (BybitAccountService) exchange.getAccountService(); + log.info( + "switch mode to one-way, result {}", + bybitAccountService.switchPositionMode(BybitCategory.LINEAR, null, "USDT", 0)); +// set leverage to 1.1 + bybitAccountService.setLeverage(ETH_PERP, 1.1); + Disposable positionChangesDisposable = + ((BybitStreamingTradeService) exchange.getStreamingTradeService()) + .getBybitPositionChanges(BybitCategory.LINEAR) + .doOnError( + error -> { + log.error(error.getMessage()); + }) + .subscribe(p -> log.info("position change {}", p)); + Disposable tradesDisposable = + exchange.getStreamingMarketDataService().getTrades(ETH_PERP) + .doOnError( + error -> { + log.error(error.getMessage()); + }) + .subscribe( + t -> log.info("trade {}", t)); + try { + Thread.sleep(1000L); + MarketOrder marketOrder = new MarketOrder(OrderType.BID, amount, ETH_PERP); + log.info("market order id: {}", exchange.getTradeService().placeMarketOrder(marketOrder)); + Thread.sleep(5000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + positionChangesDisposable.dispose(); + } + + +} + + diff --git a/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamTestNetExample.java b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamTestNetExample.java new file mode 100644 index 00000000000..ecb4fa1b718 --- /dev/null +++ b/xchange-stream-bybit/src/test/java/info/bitrich/xchangestream/bybit/example/BybitStreamTestNetExample.java @@ -0,0 +1,106 @@ +package info.bitrich.xchangestream.bybit.example; + +import static java.math.RoundingMode.UP; +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_ACCOUNT_TYPE; +import static org.knowm.xchange.bybit.BybitExchange.SPECIFIC_PARAM_TESTNET; + +import info.bitrich.xchangestream.bybit.BybitStreamingExchange; +import info.bitrich.xchangestream.bybit.BybitStreamingTradeService; +import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.core.StreamingExchangeFactory; +import io.reactivex.rxjava3.disposables.Disposable; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Date; +import java.util.concurrent.atomic.AtomicReference; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bybit.dto.BybitCategory; +import org.knowm.xchange.bybit.dto.account.walletbalance.BybitAccountType; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.derivative.FuturesContract; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.instrument.Instrument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BybitStreamTestNetExample { + + private static final Logger log = LoggerFactory.getLogger(BybitStreamTestNetExample.class); + + // Uses TEST_NET + public static void main(String[] args) { + try { + + auth(); + + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + private static final Instrument BTC_PERP = new FuturesContract("BTC/USDT/PERP"); + private static final Instrument DOGE_PERP = new FuturesContract("DOGE/USDT/PERP"); + private static final Instrument ETH_PERP = new FuturesContract("ETH/USDT/PERP"); + + private static final Instrument ETH_SPOT = new CurrencyPair("ETH/USDT"); + + private static void auth() throws IOException, InterruptedException { + ExchangeSpecification exchangeSpecification = + new BybitStreamingExchange().getDefaultExchangeSpecification(); + exchangeSpecification.setApiKey(System.getProperty("test_api_key")); + exchangeSpecification.setSecretKey(System.getProperty("test_secret_key")); + exchangeSpecification.setExchangeSpecificParametersItem( + SPECIFIC_PARAM_ACCOUNT_TYPE, BybitAccountType.UNIFIED); + exchangeSpecification.setExchangeSpecificParametersItem( + BybitStreamingExchange.EXCHANGE_TYPE, BybitCategory.LINEAR); + exchangeSpecification.setExchangeSpecificParametersItem(SPECIFIC_PARAM_TESTNET, true); + StreamingExchange exchange = + StreamingExchangeFactory.INSTANCE.createExchange(exchangeSpecification); + exchange.connect().blockingAwait(); + Ticker ticker = (exchange.getMarketDataService().getTicker(DOGE_PERP)); + BigDecimal amount = + exchange.getExchangeMetaData().getInstruments().get(DOGE_PERP).getMinimumAmount(); + // minimal amount to trade 5 usdt + if (amount.multiply(ticker.getBid()).compareTo(new BigDecimal("5.0")) <= 0) { + amount = + new BigDecimal("5") + .divide( + ticker.getBid(), + exchange.getExchangeMetaData().getInstruments().get(DOGE_PERP).getVolumeScale(), + UP); + } + LimitOrder limitOrder = + new LimitOrder(OrderType.BID, amount, DOGE_PERP, "", new Date(), ticker.getAsk()); + Thread.sleep(2000L); + AtomicReference order = new AtomicReference<>(); + Disposable disposableOrderChanges = + ((BybitStreamingTradeService) exchange.getStreamingTradeService()) + .getOrderChanges(null,BybitCategory.LINEAR) + .doOnError(error -> log.error("OrderChanges error", error)) + .subscribe( + c -> { + log.info("Order Changes {}", c); + order.set(c); + }, + throwable -> log.error("OrderChanges throwable", throwable)); + + Disposable disposableComplexPositionChanges = + ((BybitStreamingTradeService) exchange.getStreamingTradeService()) + .getBybitPositionChanges(BybitCategory.LINEAR) + .doOnError(error -> log.error("ComplexPositionChanges error {}", error, error)) + .subscribe( + p -> log.info("ComplexPositionChanges Changes {}", p), + throwable -> log.error("ComplexPosition throwable,{}", throwable.getMessage())); + Thread.sleep(3000L); + exchange.getTradeService().placeLimitOrder(limitOrder); + Thread.sleep(30000L); + disposableOrderChanges.dispose(); + disposableComplexPositionChanges.dispose(); + exchange.disconnect().blockingAwait(); + } + + +} diff --git a/xchange-stream-bybit/src/test/resources/logback.xml b/xchange-stream-bybit/src/test/resources/logback.xml index 211265d002a..bbb9ac919cd 100644 --- a/xchange-stream-bybit/src/test/resources/logback.xml +++ b/xchange-stream-bybit/src/test/resources/logback.xml @@ -17,10 +17,10 @@ - - - - - + + + + + \ No newline at end of file diff --git a/xchange-stream-bybit/src/test/resources/orderBookDeltaResponse.json5 b/xchange-stream-bybit/src/test/resources/orderBookDeltaResponse.json5 new file mode 100644 index 00000000000..00150d17128 --- /dev/null +++ b/xchange-stream-bybit/src/test/resources/orderBookDeltaResponse.json5 @@ -0,0 +1,63 @@ +{ + "topic": "orderbook.50.BTCUSDT", + "type": "delta", + "ts": 1687940967466, + "data": { + "s": "BTCUSDT", + "b": [ + [ + "30247.20", + "30.028" + ], + [ + "30245.40", + "0.224" + ], + [ + "30242.10", + "1.593" + ], + [ + "30240.30", + "1.305" + ], + [ + "30240.00", + "0" + ] + ], + "a": [ + [ + "30248.70", + "0" + ], + [ + "30249.30", + "0.892" + ], + [ + "30249.50", + "1.778" + ], + [ + "30249.60", + "0" + ], + [ + "30251.90", + "2.947" + ], + [ + "30252.20", + "0.659" + ], + [ + "30252.50", + "4.591" + ] + ], + "u": 177400507, + "seq": 66544703342 + } + "cts": 1687940967464 +} \ No newline at end of file diff --git a/xchange-stream-bybit/src/test/resources/orderBookSnapshotResponse.json5 b/xchange-stream-bybit/src/test/resources/orderBookSnapshotResponse.json5 new file mode 100644 index 00000000000..ebcfba268dd --- /dev/null +++ b/xchange-stream-bybit/src/test/resources/orderBookSnapshotResponse.json5 @@ -0,0 +1,31 @@ +{ + "topic": "orderbook.50.BTCUSDT", + "type": "snapshot", + "ts": 1672304484978, + "data": { + "s": "BTCUSDT", + "b": [ + [ + "16493.50", + "0.006" + ], + [ + "16493.00", + "0.100" + ] + ], + "a": [ + [ + "16611.00", + "0.029" + ], + [ + "16612.00", + "0.213" + ] + ], + "u": 18521288, + "seq": 7961638724 + }, + "cts": 1672304484976 +} \ No newline at end of file diff --git a/xchange-stream-bybit/src/test/resources/orderChangeResponse.json5 b/xchange-stream-bybit/src/test/resources/orderChangeResponse.json5 new file mode 100644 index 00000000000..85ad623ad95 --- /dev/null +++ b/xchange-stream-bybit/src/test/resources/orderChangeResponse.json5 @@ -0,0 +1,52 @@ +{ + "id": "5923240c6880ab-c59f-420b-9adb-3639adc9dd90", + "topic": "order", + "creationTime": 1672364262474, + "data": [ + { + "symbol": "ETH-30DEC22-1400-C", + "orderId": "5cf98598-39a7-459e-97bf-76ca765ee020", + "side": "Sell", + "orderType": "Market", + "cancelType": "UNKNOWN", + "price": "72.5", + "qty": "1", + "orderIv": "", + "timeInForce": "IOC", + "orderStatus": "Filled", + "orderLinkId": "", + "lastPriceOnCreated": "", + "reduceOnly": false, + "leavesQty": "", + "leavesValue": "", + "cumExecQty": "1", + "cumExecValue": "75", + "avgPrice": "75", + "blockTradeId": "", + "positionIdx": 0, + "cumExecFee": "0.358635", + "closedPnl": "0", + "createdTime": "1672364262444", + "updatedTime": "1672364262457", + "rejectReason": "EC_NoError", + "stopOrderType": "", + "tpslMode": "", + "triggerPrice": "", + "takeProfit": "", + "stopLoss": "", + "tpTriggerBy": "", + "slTriggerBy": "", + "tpLimitPrice": "", + "slLimitPrice": "", + "triggerDirection": 0, + "triggerBy": "", + "closeOnTrigger": false, + "category": "option", + "placeType": "price", + "smpType": "None", + "smpGroup": 0, + "smpOrderId": "", + "feeCurrency": "" + } + ] +} \ No newline at end of file diff --git a/xchange-stream-bybit/src/test/resources/positionChangeResponse.json5 b/xchange-stream-bybit/src/test/resources/positionChangeResponse.json5 new file mode 100644 index 00000000000..af91b28b8e0 --- /dev/null +++ b/xchange-stream-bybit/src/test/resources/positionChangeResponse.json5 @@ -0,0 +1,43 @@ +{ + "id": "1003076014fb7eedb-c7e6-45d6-a8c1-270f0169171a", + "topic": "position", + "creationTime": 1697682317044, + "data": [ + { + "positionIdx": 2, + "tradeMode": 0, + "riskId": 1, + "riskLimitValue": "2000000", + "symbol": "BTCUSDT", + "side": "", + "size": "0", + "entryPrice": "0", + "leverage": "10", + "positionValue": "0", + "positionBalance": "0", + "markPrice": "28184.5", + "positionIM": "0", + "positionMM": "0", + "takeProfit": "0", + "stopLoss": "0", + "trailingStop": "0", + "unrealisedPnl": "0", + "curRealisedPnl": "1.26", + "cumRealisedPnl": "-25.06579337", + "sessionAvgPrice": "0", + "createdTime": "1694402496913", + "updatedTime": "1697682317038", + "tpslMode": "Full", + "liqPrice": "0", + "bustPrice": "", + "category": "linear", + "positionStatus": "Normal", + "adlRankIndicator": 0, + "autoAddMargin": 0, + "leverageSysUpdatedTime": "", + "mmrSysUpdatedTime": "", + "seq": 8327597863, + "isReduceOnly": false + } + ] +} \ No newline at end of file diff --git a/xchange-stream-bybit/src/test/resources/tradeResponse.json5 b/xchange-stream-bybit/src/test/resources/tradeResponse.json5 new file mode 100644 index 00000000000..519527a0619 --- /dev/null +++ b/xchange-stream-bybit/src/test/resources/tradeResponse.json5 @@ -0,0 +1,17 @@ +{ + "topic": "publicTrade.BTCUSDT", + "type": "snapshot", + "ts": 1672304486868, + "data": [ + { + "T": 1672304486865, + "s": "BTCUSDT", + "S": "Buy", + "v": "0.001", + "p": "16578.50", + "L": "PlusTick", + "i": "20f43950-d8dd-5b31-9112-a178eb6023af", + "BT": false + } + ] +} \ No newline at end of file From 262d4028b3ec7d20f5019b39adec03e7698d2487 Mon Sep 17 00:00:00 2001 From: rizer1980 <4340180@gmail.com> Date: Wed, 18 Dec 2024 17:52:59 +0300 Subject: [PATCH 2/3] [bybit-stream] fix logback --- xchange-bybit/src/test/resources/logback.xml | 4 ++-- xchange-stream-bybit/src/test/resources/logback.xml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/xchange-bybit/src/test/resources/logback.xml b/xchange-bybit/src/test/resources/logback.xml index da2d1b62c3c..2a586567a6e 100644 --- a/xchange-bybit/src/test/resources/logback.xml +++ b/xchange-bybit/src/test/resources/logback.xml @@ -17,7 +17,7 @@ - - + + \ No newline at end of file diff --git a/xchange-stream-bybit/src/test/resources/logback.xml b/xchange-stream-bybit/src/test/resources/logback.xml index bbb9ac919cd..211265d002a 100644 --- a/xchange-stream-bybit/src/test/resources/logback.xml +++ b/xchange-stream-bybit/src/test/resources/logback.xml @@ -17,10 +17,10 @@ - - - - - + + + + + \ No newline at end of file From c410d69480307cbad308a3ac46c4901dc81e02f5 Mon Sep 17 00:00:00 2001 From: rizer1980 <4340180@gmail.com> Date: Mon, 23 Dec 2024 22:33:15 +0300 Subject: [PATCH 3/3] [bybit-stream] add ChannelInactiveHandler --- .../bybit/service/BybitTradeServiceRaw.java | 44 ------------------- .../bybit/BybitStreamingExchange.java | 10 +++++ .../bybit/BybitStreamingService.java | 7 +++ 3 files changed, 17 insertions(+), 44 deletions(-) diff --git a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java index b972dafa8bf..09ffd276fd1 100644 --- a/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java +++ b/xchange-bybit/src/main/java/org/knowm/xchange/bybit/service/BybitTradeServiceRaw.java @@ -54,50 +54,6 @@ BybitResult> getBybitOrder( return order; } -// BybitResult placeMarketOrder( -// BybitCategory category, String symbol, BybitSide side, BigDecimal qty, String orderLinkId) -// throws IOException { -// BybitPlaceOrderPayload payload = -// new BybitPlaceOrderPayload(category, symbol, side, MARKET, qty, orderLinkId); -// BybitResult placeOrder = -// decorateApiCall( -// () -> bybitAuthenticated.placeMarketOrder(apiKey, signatureCreator, nonceFactory, -// payload)) -// .withRateLimiter(getCreateOrderRateLimiter(category)) -// .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) -// .call(); -// if (!placeOrder.isSuccess()) { -// throw createBybitExceptionFromResult(placeOrder); -// } -// return placeOrder; -// } - -//BybitResult placeLimitOrder( -// BybitCategory category, -// String symbol, -// BybitSide side, -// BigDecimal qty, -// BigDecimal limitPrice, -// String orderLinkId, -// boolean reduceOnly) -// throws IOException { -// BybitPlaceOrderPayload payload = -// new BybitPlaceOrderPayload( -// category, symbol, side, BybitOrderType.LIMIT, qty, orderLinkId, limitPrice); -// payload.setReduceOnly(String.valueOf(reduceOnly)); -// BybitResult placeOrder = -// decorateApiCall( -// () -> bybitAuthenticated.placeLimitOrder(apiKey, signatureCreator, nonceFactory, -// payload)) -// .withRateLimiter(getCreateOrderRateLimiter(category)) -// .withRateLimiter(rateLimiter(GLOBAL_RATE_LIMITER)) -// .call(); -// if (!placeOrder.isSuccess()) { -// throw createBybitExceptionFromResult(placeOrder); -// } -// return placeOrder; -// } - BybitResult amendOrder( BybitCategory category, String symbol, diff --git a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java index c9fe13147e2..e1956902bc2 100644 --- a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java +++ b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingExchange.java @@ -2,6 +2,7 @@ import info.bitrich.xchangestream.core.ProductSubscription; import info.bitrich.xchangestream.core.StreamingExchange; +import info.bitrich.xchangestream.service.netty.WebSocketClientHandler; import io.reactivex.rxjava3.core.Completable; import org.knowm.xchange.bybit.BybitExchange; import org.knowm.xchange.bybit.dto.BybitCategory; @@ -95,4 +96,13 @@ public BybitStreamingTradeService getStreamingTradeService() { return streamingTradeService; } + /** + * Enables the user to listen on channel inactive events and react appropriately. + * + * @param channelInactiveHandler a WebSocketMessageHandler instance. + */ + public void setChannelInactiveHandler( + WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler) { + streamingService.setChannelInactiveHandler(channelInactiveHandler); + } } diff --git a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingService.java b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingService.java index be7ece0918d..4125cafbe80 100644 --- a/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingService.java +++ b/xchange-stream-bybit/src/main/java/info/bitrich/xchangestream/bybit/BybitStreamingService.java @@ -8,6 +8,7 @@ import dto.BybitSubscribeMessage; import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService; import info.bitrich.xchangestream.service.netty.WebSocketClientCompressionAllowClientNoContextAndServerNoContextHandler; +import info.bitrich.xchangestream.service.netty.WebSocketClientHandler; import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler; import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.CompletableSource; @@ -41,6 +42,7 @@ public class BybitStreamingService extends JsonNettyStreamingService { private final Observable pingPongSrc = Observable.interval(15, 20, TimeUnit.SECONDS); private Disposable pingPongSubscription; private final ExchangeSpecification spec; + private WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler = null; @Getter private boolean isAuthorized = false; public BybitStreamingService(String apiUrl, ExchangeSpecification spec) { @@ -158,4 +160,9 @@ public void pingPongDisconnectIfConnected() { protected WebSocketClientExtensionHandler getWebSocketClientExtensionHandler() { return WebSocketClientCompressionAllowClientNoContextAndServerNoContextHandler.INSTANCE; } + + public void setChannelInactiveHandler( + WebSocketClientHandler.WebSocketMessageHandler channelInactiveHandler) { + this.channelInactiveHandler = channelInactiveHandler; + } }